了解了是什么,接下来看怎么用。很简单,如图,就是插管子,也就是用流,对流进行操作。比如:往文件(外部)上插一根输出管 new FileOutputStream(“文件路径”) ,然后 “内部” 往管子上写数据。
try {
FileOutputStream outputStream = new FileOutputStream("./new.txt")
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
上面代码就是一个最简单的输出操作,当然这是不完整。大家都知道,文件打开了就必须要关闭,那么什么是文件打开,什么是文件关闭,为什么要关闭呢?
那么为什么要关闭就明显了,然后把关闭给加上就是下面这样了:
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream("./new.txt");
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
为什么要写在 finally 而不写在 try 里面呢?因为如果代码有问题就有可能执行不到 close() 了。
但这样好麻烦啊,而且都是固定代码,又不能减少。别着急,Java7 引入了新方式,在 try 里面就可以直接做回收。如下:
try (FileOutputStream outputStream = new FileOutputStream("./new.txt")) {
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
当然,能写在 try() 里面的有个限制,必须是实现了 Closeable 接口的类。
能插输出的管子来写,肯定也能插输入的管子来读:
try (FileInputStream inputStream = new FileInputStream("./new.txt")) {
System.out.print((char) inputStream.read());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try (FileOutputStream outputStream = new FileOutputStream("./new.txt");
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
bufferedWriter.write("x");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
输入,读的操作
try (FileInputStream inputStream = new FileInputStream("./new.txt");
InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader)) {
System.out.println(bufferedReader.readLine());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
可以看到这就是一个管道插在另一个管道上,最后插在文件上,进行文件的读写操作。代码中用到了缓冲(buffer),在 BufferedReader 和 BufferedWriter 源码中都会有定义这个缓冲的大小:
...
private static int defaultCharBufferSize = 8192;
...
就上面的代码而言,BufferedReader 用到了缓冲,当然 BufferedWriter 也用了缓冲,只有当 buffer 中的数据大小达到 8192 个的时候才会往文件中写。
那如果是自动的话,是不是说每次都可以不写 flush 呢?也不是,接下来介绍一种要用到 flush 的情况:
// 模拟一个服务器,读到什么内容就写什么内容返回
try {
ServerSocket serverSocket = new ServerSocket(8080);
// 等待别人的请求,阻塞式的
Socket socket = serverSocket.accept();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String data;
while (true) {
data = reader.readLine();
writer.write(data);
// 冲马桶行为。这个时候就不能等关闭的时候自动冲了。
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
最后说一下文件的复制,怎么做文件复制呢?原理就是一个字节一个字节的搬。从一个文件读数据,写到另一个文件去。
try (FileOutputStream outputStream = new FileOutputStream("./new_copy.txt");
FileInputStream inputStream = new FileInputStream("./new.txt")) {
byte[] bytes = new byte[1024];
int size;
// 每次记录读取到的数据 size
while ((size = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, size);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
这里需要考虑一个情况,当读到最后一次,剩余数据的大小不足1024的时候,bytes 中会有残留的上一次的数据。比如:new.txt 中一共有 1025 个,第一次读了1024,第二次读的时候本应该只有一个,但是除了 0 位置上的数据变了之外,后面所有的数据还是上一次读到的数据。所以要记录读到的大小。
本文介绍了 Java I/O 是什么,怎么用,原理就是插管子(通过流进行对文件或网络的读写操作)。也可以往管子上再插管子。理解了这些,我相信以后再也不要在用到 I/O 的时候到网上复制粘贴了。
(ps:原本还准备把 NIO原理,Okio使用也做个总结,想不到,想不到。。。输出比输入难啊 - -)