Java IO

包装流的close方法是否会自动关闭被包装的流?

平时使用输入流和输出流一般都会使用buffer包装一下,直接看下面代码(这个代码运行正常,不会报错)

  1. import java.io.BufferedOutputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. public class IOTest {
  5. public static void main(String[] args) throws IOException {
  6. FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
  7. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
  8. bufferedOutputStream.write("test write something".getBytes());
  9. bufferedOutputStream.flush();
  10. //从包装流中关闭流
  11. bufferedOutputStream.close();
  12. }
  13. }

下面来研究下这段代码的bufferedOutputStream.close();方法是否调用了fileOutputStream.close();
先看BufferedOutputStream源代码:

  1. public class BufferedOutputStream extends FilterOutputStream { ...

可以看到它继承FilterOutputStream,并且没有重写close方法,所以直接看FilterOutputStream的源代码:

  1. public void close() throws IOException {
  2. try {
  3. flush();
  4. } catch (IOException ignored) {
  5. }
  6. out.close();
  7. }

跟踪out(FilterOutputStream中):

  1. protected OutputStream out;
  2. public FilterOutputStream(OutputStream out) {
  3. this.out = out;
  4. }

再看看BufferedOutputStream中:

  1. public BufferedOutputStream(OutputStream out) {
  2. this(out, 8192);
  3. }
  4. public BufferedOutputStream(OutputStream out, int size) {
  5. super(out);
  6. if (size <= 0) {
  7. throw new IllegalArgumentException("Buffer size <= 0");
  8. }
  9. buf = new byte[size];
  10. }

可以看到BufferedOutputStream调用super(out);,也就是说,out.close();调用的是通过BufferedOutputStream传入的被包装的流,这里就是FileOutputStream
在看看其他类似的,比如BufferedWriter的源代码:

  1. public void close() throws IOException {
  2. synchronized (lock) {
  3. if (out == null) {
  4. return;
  5. }
  6. try {
  7. flushBuffer();
  8. } finally {
  9. out.close();
  10. out = null;
  11. cb = null;
  12. }
  13. }
  14. }

通过观察各种流的源代码,可得结论:包装的流都会自动调用被包装的流的关闭方法,无需自己调用。

关闭流方法是否有顺序?

由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上习惯都是两个流都关闭的。
首先来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:

1.先关闭被包装流(正常没异常抛出)

  1. import java.io.BufferedOutputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. public class IOTest {
  5. public static void main(String[] args) throws IOException {
  6. FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
  7. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
  8. bufferedOutputStream.write("test write something".getBytes());
  9. bufferedOutputStream.flush();
  10. fileOutputStream.close();//先关闭被包装流
  11. bufferedOutputStream.close();
  12. }
  13. }

2.先关闭包装流(正常没异常抛出)

  1. import java.io.BufferedOutputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. public class IOTest {
  5. public static void main(String[] args) throws IOException {
  6. FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
  7. BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
  8. bufferedOutputStream.write("test write something".getBytes());
  9. bufferedOutputStream.flush();
  10. bufferedOutputStream.close();//先关闭包装流
  11. fileOutputStream.close();
  12. }
  13. }

上述两种写法都没有问题,已经知道bufferedOutputStream.close();会自动调用fileOutputStream.close();方法,那么这个方法是怎么执行的呢?看看FileOutputStream的源码:

  1. public void close() throws IOException {
  2. synchronized (closeLock) {
  3. if (closed) {
  4. return;
  5. }
  6. closed = true;
  7. }
  8. ...

可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题。
如果没有看过参考文章,可能就会断下结论,关闭流不需要考虑顺序
看下下面的代码:

  1. import java.io.BufferedWriter;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStreamWriter;
  5. public class IOTest {
  6. public static void main(String[] args) throws IOException {
  7. FileOutputStream fos = new FileOutputStream("c:\\a.txt");
  8. OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
  9. BufferedWriter bw = new BufferedWriter(osw);
  10. bw.write("java IO close test");
  11. // 从内带外顺序顺序会报异常
  12. fos.close();
  13. osw.close();
  14. bw.close();
  15. }
  16. }

会抛出Stream closed的IO异常:

  1. Exception in thread "main" java.io.IOException: Stream closed
  2. at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:45)
  3. at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:118)
  4. at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
  5. at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
  6. at java.io.BufferedWriter.close(BufferedWriter.java:264)
  7. at IOTest.main(IOTest.java:18)

而如果把bw.close();放在第一,其他顺序任意,即修改成下面两种:

  1. bw.close();
  2. osw.close();
  3. fos.close();
  4. bw.close();
  5. fos.close();
  6. osw.close();

都不会报错,这是为什么呢,看看BufferedWriterclose源码:

  1. public void close() throws IOException {
  2. synchronized (lock) {
  3. if (out == null) {
  4. return;
  5. }
  6. try {
  7. flushBuffer();
  8. } finally {
  9. out.close();
  10. out = null;
  11. cb = null;
  12. }
  13. }
  14. }

里面调用了flushBuffer()方法,也是抛异常中的错误方法:

  1. void flushBuffer() throws IOException {
  2. synchronized (lock) {
  3. ensureOpen();
  4. if (nextChar == 0)
  5. return;
  6. out.write(cb, 0, nextChar);
  7. nextChar = 0;
  8. }
  9. }

可以看到很大的一行

  1. out.write(cb, 0, nextChar);

这行如果在流关闭后执行就会抛IO异常,有时候会写成:

  1. fos.close();
  2. fos = null;
  3. osw.close();
  4. osw = null;
  5. bw.close();
  6. bw = null;

这样也会抛异常,不过是由于flushBuffer()ensureOpen()抛的,可从源码中看出:

  1. private void ensureOpen() throws IOException {
  2. if (out == null)
  3. throw new IOException("Stream closed");
  4. }
  5. void flushBuffer() throws IOException {
  6. synchronized (lock) {
  7. ensureOpen();
  8. if (nextChar == 0)
  9. return;
  10. out.write(cb, 0, nextChar);
  11. nextChar = 0;
  12. }
  13. }

如何防止这种情况?
直接写下面这种形式就可以:

  1. bw.close();
  2. bw = null;

:::success 结论:一个流上的close方法可以多次调用,理论上关闭流不需要考虑顺序,但有时候关闭方法中调用了write等方法时会抛异常。 :::


由上述的两个结论可以得出下面的建议:
关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。如:

  1. bw.close();
  2. //下面三个无顺序
  3. osw = null;
  4. fos = null;
  5. bw = null;

注意的是,有些方法中close方法除了调用被包装流的close方法外还会把包装流置为null,方便JVM回收。bw.close()中的:

  1. public void close() throws IOException {
  2. synchronized (lock) {
  3. if (out == null) {
  4. return;
  5. }
  6. try {
  7. flushBuffer();
  8. } finally {
  9. out.close();
  10. out = null;
  11. cb = null;
  12. }
  13. }
  14. }

finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(建议还是写一下)