Java中常用的IO操作基本上可以分为四大部分,分别是:File类操作、RandomAccessFile类操作、字节流操作、字符流操作。只要熟练掌握了本文中所列举的所有例子,基本上对于Java的IO流操作就可以说是掌握了。
下面将以JUnit测试用例的方式,用一个个例子的方式列出这四大部分中常用的操作例子。
一、File类操作
File类操作定义了最基本的、与操作系统的稳健系统相关的操作,可以对文件夹、文件进行一系列的操作。
1、常用的一些用法
//1.两个常量 @Test public void twoConstant(){ //输出: ‘/‘ ‘:‘ System.out.println(File.separator); System.out.println(File.pathSeparator); } //2.创建新文件 @Test public void createFile(){ File f = new File("hello"); try{ f.createNewFile(); System.out.println("创建了文件:" + f.getAbsolutePath()); }catch(Exception e){ e.printStackTrace(); } } //3.创建一个文件夹 @Test public void createFolder(){ try{ File file = new File("HelloFolder"); //当一个目录下既没有该名字对应的目录和文件时,才能建立该文件夹 if(!file.isDirectory() && !file.isFile()){ file.mkdir(); //file.mkdirs(); //如果上级目录不存在,那么一并创建上级目录 }else{ System.out.println("已存在该名称的文件或文件夹"); } }catch(Exception e){ e.printStackTrace(); } } //4.删除一个文件 @Test public void deleteFile(){ try{ File file = new File("hello"); if(file.exists()){ file.delete(); }else{ System.out.println("文件不存在"); } }catch(Exception e){ e.printStackTrace(); } } //5.删除一个文件夹(与删除文件一样) @Test public void deleteFolder(){ try{ File file = new File("HelloFolder"); if(file.exists()){ file.delete(); }else{ System.out.println("该文件夹不存在"); } }catch(Exception e){ e.printStackTrace(); } } //6.获取指定目录下所有文件的文件名(包括隐藏文件和文件夹) @Test public void getAllFileName(){ try{ File folder = new File("."); //当前目录 String[] fileStrs = folder.list(); for(String str : fileStrs){ System.out.print(str + " "); } }catch(Exception e){ e.printStackTrace(); } } //7.获取指定目录下所有文件的路径 @Test public void getAllFilePath(){ try{ File folder = new File("."); //当前目录 File[] files = folder.listFiles(); for(File file : files){ System.out.println(file.getCanonicalPath()); } }catch(Exception e){ e.printStackTrace(); } }
2、打印指定目录下的所有文件(递归调用)
package com.chanshuyi.io; /** * 列出指定目录的全部内容 * */ import java.io.*; class ListAllFile{ public static void main(String[] args) { File f = new File("."); print(f); } //递归打印 public static void print(File f){ if(f != null){ if(f.isDirectory()){ File[] fileArray = f.listFiles(); if(fileArray != null){ for(int i = 0; i < fileArray.length; i++){ print(fileArray[i]); } } }else{ System.out.println(f); } } } }
二、RandomAccessFile类操作
RandomAccessFile类可以对文件进行随机访问,比如可以指定读写指针到某一个字节处,也可以读写指针指定跳过指定字节数。简单地说,RandomAccessFile类提供了许多方法,使得我们可以对文件进行更加细致的读写操作。
//8.随机读写文件类 @Test public void operateRandom(){ try{ //1.跳过两个字节读取文件内容 //文件内容是:Hello, this is demo File. RandomAccessFile randomRW = new RandomAccessFile(new File("demo.txt"), "rw"); randomRW.skipBytes(2); //跳过两个字节,即跳过了‘he‘两个英文字符(一个英文字符占用1个字节) byte b[] = new byte[100]; int length = randomRW.read(b); System.out.println("总共读取了" + length + "个字节,读取的内容是:" + new String(b)); //总共读取了23个字节,读取的内容是:llo, this is demo File. //2.将读写指针跳回文件头重新读取 randomRW.seek(0); length = randomRW.read(b); System.out.println("总共读取了" + length + "个字节,读取的内容是:" + new String(b)); //总共读取了25个字节,读取的内容是:Hello, this is demo File. //3.获取读写指针所在地址 long pointer = randomRW.getFilePointer(); System.out.println("文件指针地址:" + pointer); //文件指针地址:25 randomRW.seek(2); pointer = randomRW.getFilePointer(); System.out.println("文件指针地址:" + pointer); //文件指针地址:2 randomRW.seek(77); pointer = randomRW.getFilePointer(); System.out.println("文件指针地址:" + pointer); //文件指针地址:77 randomRW.close(); }catch(Exception e){ e.printStackTrace(); }
三、字节流读写
FileInputStream / FileOutputStream - 实现了对文件的读写操作
ObjectInpuStream / ObjectOutputStream - 实现了对序列化对象的读写操作
ByteArrayInputStream / ByteArrayOutputStream - 实现了对字节数组的读写操作
PipedInputStream / PipedOutputStream - 实现不同线程之间的通信
SequenceInputStream - 实现不同输入流的合并(此对象没有对应的OutputStream类)
BufferedInputStream / BufferedOutputStream - 实现读写缓存层
1、字节流读取 - byte(表示读取的数据类型是byte或byte[])
//15.字节流读取 - byte @Test public void readByte(){ try{ File file = new File("hello.txt"); InputStream fos = new FileInputStream(file); byte[] bytes = new byte[fos.available()]; fos.read(bytes); String str = new String(bytes); System.out.println("文件内容是:\n" + str); fos.close(); }catch(Exception e){ e.printStackTrace(); } }
2、字节流读取(缓存) - byte
//字节流读取(缓存) - byte @Test public void writeByteWithBuffere(){ try{ File file = new File("hello1.txt"); OutputStream fos = new FileOutputStream(file); BufferedOutputStream out = new BufferedOutputStream(fos); out.write("你好\n".getBytes()); out.write("吃饭了么!".getBytes()); out.flush(); fos.close(); }catch(Exception e){ e.printStackTrace(); } }
3、字节流写入 - byte
//17.字节流写入 - byte @Test public void writeByte(){ try{ File file = new File("hello.txt"); OutputStream fos = new FileOutputStream(file); fos.write("Hello Mac.\n你好,Mac.".getBytes()); fos.close(); }catch(Exception e){ e.printStackTrace(); } }
4、字节流写入(缓存) - byte
//18.字节流写入(缓存) - byte @Test public void writeByteWithBuffer(){ try{ File file = new File("hello.txt"); OutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); bos.write("Hello Mac.\n你好,Mac.".getBytes()); bos.close(); fos.close(); }catch(Exception e){ e.printStackTrace(); } }
如果你仔细敲过上面4个例子的代码你会发现,其实好像字节流的读取和写入,好像加了缓存和没加缓存,它们的代码好像都差不多啊,至少再写入数据的时候是一样的。而字符流的读取在加了缓存层之后,至少还能直接读取整行数据,字符流的写入加了缓存之后,可以写入换行符。那字节流的缓存究竟有什么必要性呢?
确实,从代码以及其方法上看,其实他们并没有什么区别,但是从官方的API文档来看,缓存最重要的一个地方就是减少程序对于磁盘的IO次数。加了缓存的程序再读取的时候会一次性读取很多个字节,之后提供给程序使用,但如果你不加缓存,那么程序就只会读取代码中指定的字节数。这在你一个字节一个字节从文件中读取数据的时候,其差别就凸现出来了。如果你没有使用缓存进行数据读取,那么你每读一个字节的数据,程序就去磁盘读取一次文件,这样会造成磁盘的频繁IO读取,减少磁盘的寿命。
5、ObjectInputStream / ObjectOutputStream - 序列化一个对象
要被序列化的POJO对象:
package com.chanshuyi.io.po; import java.io.Serializable; public class Student implements Serializable{ /** * 序列化ID */ private static final long serialVersionUID = 7288449352920655248L; private String name; private int age; private String phone; public Student(){ } public Student(String name, int age, String phone){ this.name = name; this.age = age; this.phone = phone; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String toString(){ return name + "," + age + "," + phone; } }
被序列化的POJO对象需要实现Serializable接口。
序列化对象方法:
//20.ObjectOutputStream - 序列化对象 - 将对象属性序列化保存 @Test public void serialized(){ try{ Student std = new Student("Tommy", 13, "18923923876"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("StudentObject.obj")); oos.writeObject(std); oos.close(); }catch(Exception e){ e.printStackTrace(); } }
反序列化对象方法:
//21.ObjectInputStream - 反序列化对象 - 读取序列化后的对象 @Test public void deSerialized(){ try{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("StudentObject.obj")); Student std = (Student)ois.readObject(); System.out.println(std); ois.close(); }catch(Exception e){ e.printStackTrace(); } }
6、ByteArrayInputStream / ByteArrayOutputStream - 操作内存字节数据
//19.ByteArrayInputStream - 内存操作流 - 将内存数据转化为流 @Test public void random2Stream(){ try{ String str = "你好"; ByteArrayInputStream input = new ByteArrayInputStream(str.getBytes()); ByteArrayOutputStream output = new ByteArrayOutputStream(); int temp = 0; while((temp = input.read()) != (-1)){ char ch = (char)temp; output.write(Character.toLowerCase(ch)); } String outputStr = output.toString(); input.close(); output.close(); System.out.println(outputStr); }catch(Exception e){ e.printStackTrace(); } }
7、PipedInputStream / PipedOutputStream - 实现进程间的管道通信
接收者:
package com.chanshuyi.io.pineStream; import java.io.PipedInputStream; /** * 管道流 - 接收者 * @author yurongchan * */ public class Receiver implements Runnable{ private PipedInputStream in = null; public Receiver(){ in = new PipedInputStream(); } public PipedInputStream getIn(){ return this.in; } public void run(){ byte[] b = new byte[1000]; int length = 0; try{ length = this.in.read(b); }catch(Exception e){ e.printStackTrace(); } System.out.println("接收到的消息:" + new String(b, 0, length)); } }
发送者:
package com.chanshuyi.io.pineStream; import java.io.PipedOutputStream; /** * 管道流 - 发送者 * @author yurongchan * */ public class Send implements Runnable{ private PipedOutputStream out = null; public Send(){ out = new PipedOutputStream(); } public PipedOutputStream getOut(){ return this.out; } public void run(){ String msg = "Hello, I‘m outputer.\n你好,我是发送者。"; try{ out.write(msg.getBytes()); out.close(); }catch(Exception e){ e.printStackTrace(); } } }
测试方法:
//22.PipedInputStream - 在不同线程之间进行通信 @Test public void pipeContact(){ try{ Send send = new Send(); Receiver receiver = new Receiver(); try{ send.getOut().connect(receiver.getIn()); }catch(Exception e){ e.printStackTrace(); } new Thread(send).start(); new Thread(receiver).start(); }catch(Exception e){ e.printStackTrace(); } }
8、SequenceInputStream - 合并输入流
//23.SequenceInputStream - 合并几个输入流 @Test public void sequenceInputStream(){ try{ InputStream is1 = new FileInputStream(new File("sequence1.txt")); InputStream is2 = new FileInputStream(new File("sequence2.txt")); OutputStream os = new FileOutputStream(new File("sequence3.txt")); SequenceInputStream sis = new SequenceInputStream(is1, is2); int temp = 0; while((temp = sis.read()) != -1){ os.write(temp); } sis.close(); is1.close(); is2.close(); os.close(); }catch(Exception e){ e.printStackTrace(); } }
之后打开sequence3.txt会发现,1、2文本中的内容都到了sequence3.txt中了。
四、字符流读写
字符流的读写实际上还是基于字节流实现的,而且数组存储时更多是以字节为单位存储,因此更多时候还是使用字节流进行读写操作。
因此对于字符流的读写,我们只需要掌握常用的几个操作即可。
InputStreamReader / OutputStreamWriter - 实现字符流的读写
BufferedReader / BufferedWriter - 实现字符流的缓存层
FileReader / FileWriter - 字符流的工具类
1、字符流读取 - char(表示读取的数据类型是char或char[])
//9.字符流读取 - char @Test public void writeChar(){ try{ InputStreamReader reader = new InputStreamReader(new FileInputStream("OutputStreamWriter.txt")); char[] chars = new char[1000]; int length = reader.read(chars); System.out.println("一共读取了" + length + "个字符,内容是:" + new String(chars)); reader.close(); }catch(Exception e){ e.printStackTrace(); } }
2、字符流读取(缓存) - char
//10.字符流读取(缓存) @Test public void writeCharWithBuffer(){ try{ InputStreamReader isr = new InputStreamReader(new FileInputStream("BufferedWriter.txt")); BufferedReader reader = new BufferedReader(isr); String str = ""; while((str = reader.readLine()) != null && str.length() != 0){ System.out.println(str); } reader.close(); }catch(Exception e){ e.printStackTrace(); } }
3、字符流写入 - char / char[] / String
//11.字符流写入 @Test public void readChar(){ try{ OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("OutputStreamWriter.txt")); writer.write("AllFileTest.fileReadUtil -> 字符流写入文件"); writer.close(); }catch(Exception e){ e.printStackTrace(); } }
4、字符流的写入(缓存)- char/ String
//12.字符流写入(缓存) @Test public void readCharWithBuffer(){ try{ OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("BufferedWriter.txt")); BufferedWriter writer = new BufferedWriter(osw); writer.write("AllFileTest.fileReadUtil -> 字符流写入文件"); writer.newLine(); //换行 writer.write("第二行"); writer.close(); }catch(Exception e){ e.printStackTrace(); } }
总结一下上面四种方式的字符流写入写出,我们会发现两个规律,一个是读写规律,一个是缓存层的规律:
· 读写规律。字符流的读取,其读取的数据都是char(字符型)的单个或者数组。而字符流的写入除了可以写入单个或多个char类型的字符歪,还可以直接写入String(字符串)类型。
· 缓存层规律。字符流的读写,增加了缓存层之后的一个明显区别就是:加了缓存层(Buffer)之后,字符流读取可以实现整行读取(readLine),而字符流写入可以实现写入换行符(writeLine)。
5、文件读取工具类
//13.FileReader - 文件读取工具类(这是加了缓存的。但也可以不加缓存) @Test public void fileReadUtil(){ try{ FileReader fr = new FileReader(new File("demo.txt")); BufferedReader reader = new BufferedReader(fr); String str = ""; while((str = reader.readLine()) != null && str.length() != 0){ System.out.println(str); } reader.close(); }catch(Exception e){ e.printStackTrace(); } }
将这个例子与上面的第2个例子,即加了缓存的字符流读取例子相比,你会发现其实这两个例子的区别就只是声明FileReader、InputStreamReader的区别而已。声明FileReader只需要再加上File类型参数即可,而声明InputStreamReader则需要再加上一个FileInputStream类,之后才能跟上File类型的参数。因此,我们才说FileReader是一个文件读取工具类。
6、文件写入工具类
//14.FileWriter - 文件写入工具类(这是加了缓存的。但也可以不加缓存) @Test public void fileWriteUtil(){ try{ FileWriter fw = new FileWriter(new File("FileWriteUtil1.txt")); BufferedWriter writer = new BufferedWriter(fw); writer.write("HelloMan1"); writer.newLine(); writer.write("HelloMan2"); writer.close(); }catch(Exception e){ e.printStackTrace(); } }
同样的,其实FileWriter与OutputStreamWriter也只是声明上的区别而已。
五、其他
这里会收集一些不怎么常用,但是有时也会用到的例子,遇到的时候可以快速的查询到。
1、追加新的内容
//9.字节流 - 追加新内容 //无论是字节流还是字符流,要追加新的内容都是再FileOutputStream中指定第二个参数为true @Test public void appendFile(){ try{ File file = new File("hello.txt"); OutputStream fos = new FileOutputStream(file, true); String str = "\n这是新增加的内容"; fos.write(str.getBytes()); fos.close(); }catch(Exception e){ e.printStackTrace(); } }
2、模拟打印流输出数据
//18.打印流 - 模拟打印的方式输出数据 @Test public void printStream(){ try{ PrintStream print = new PrintStream(new FileOutputStream(new File("PrintStream.txt"))); print.println(true); print.println("您好,我是打印输出流"); print.printf("名字:%s.年龄:%d", "Tom", 32); //格式化输出 print.close(); //这里的数据要再PrintStream.txt文件中才能看到 }catch(Exception e){ e.printStackTrace(); } }
3、使用OutputStream向屏幕输出内容
//19.使用OutputStream向屏幕输出内容 @Test public void systemOutStream(){ try{ OutputStream out = System.out; out.write("你好".getBytes()); out.close(); }catch(Exception e){ e.printStackTrace(); } }
4、标准输出重定向
//20.标准输出重定向 @Test public void redirectOutput(){ try{ System.out.println("Print in the Screen. 你好"); System.setOut(new PrintStream(new FileOutputStream(new File("RedirectOutput.txt")))); //下面的输出都将重定向到文件中 System.out.println("=== 重定向后的输出 ==="); System.out.println("Hello, Eclipse in Mac."); }catch(Exception e){ e.printStackTrace(); } }
5、标准输入重定向
//21.标准输入重定向 @Test public void redirectInput(){ try{ File file = new File("RedirectOutput.txt"); if(!file.exists()){ System.out.println("文件不存在!"); return; }else{ System.setIn(new FileInputStream(file)); byte b[] = new byte[1000]; int length = System.in.read(b); System.out.println("读入的内容为:" + new String(b, 0, length)); } }catch(Exception e){ e.printStackTrace(); } }
6、错误输出重定向
//22.错误输出重定向 @Test public void redirectErrOutput(){ try{ System.err.println("Print in the Screen. 你好"); System.setErr(new PrintStream(new FileOutputStream(new File("RedirectErrOutput.txt")))); //下面的输出都将重定向到文件中 System.err.println("=== 重定向后的错误输出 ==="); System.err.println("Hello, Eclipse in Mac."); }catch(Exception e){ e.printStackTrace(); } }
感谢以下博文的参考:
1、http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html
2、http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html