Java IO流关闭问题的深入研究

前几天看了一篇文章(见参考文章),自己动手试了下,发现有些不一样结论,作博客记录下,本文主要研究两个问题:

  1. 包装流的close方法是否会自动关闭被包装的流?
  2. 关闭流方法是否有顺序?

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

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

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class IOTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
        BufferedOutputStream bufferedOutputStream = new 
        BufferedOutputStream(fileOutputStream);

        bufferedOutputStream.write("test write something".getBytes());
        bufferedOutputStream.flush();

        //从包装流中关闭流
        bufferedOutputStream.close();
    }
}

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

public class BufferedOutputStream extends FilterOutputStream {
    // ...
}

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

public void close() throws IOException {
    try {
        flush();
    } catch (IOException ignored) {
    }
    out.close();
}

跟踪out(FilterOutputStream中):

protected OutputStream out;

public FilterOutputStream(OutputStream out) {
    this.out = out;
}

再看看BufferedOutputStream中:

public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}

public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

可以看到BufferedOutputStream调用super(out);,也就是说,out.close();调用的是通过BufferedOutputStream传入的被包装的流,这里就是FileOutputStream。

我们在看看其他类似的,比如BufferedWriter的源代码:

public void close() throws IOException {
  synchronized (lock) {
    if (out == null) {
      return;
    }
    try {
      flushBuffer();
    } finally {
      out.close();
      out = null;
      cb = null;
    }
  }
}

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

关闭流方法是否有顺序?

由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上我们习惯都是两个流都关闭的。

首先我们来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;


public class IOTest {
    public static void main(String[] args) throws IOException {
    
        FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        
        bufferedOutputStream.write("test write something".getBytes());
        bufferedOutputStream.flush();
        
        //情况1:先关闭被包装流
        fileOutputStream.close();
        bufferedOutputStream.close();

        //情况2:先关闭包装流
        bufferedOutputStream.close();
        fileOutputStream.close();
    }
}

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

public void close() throws IOException {
  synchronized (closeLock) {
  if (closed) {
    return;
  }
  closed = true;
}

可以看出它采用同步锁,而且使用了关闭标记,如果已经关闭了则不会再次操作,所以多次调用不会出现问题

修正:关闭流的顺序

如果没有看过参考文章,我可能就会断下结论,关闭流不需要考虑顺序。我们看下下面的代码(修改自参考文章):

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class IOTest {
  public static void main(String[] args) throws IOException {

    FileOutputStream fos = new FileOutputStream("c:\\a.txt");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
    BufferedWriter bw = new BufferedWriter(osw);
    bw.write("java IO close test");

    // 从内带外顺序顺序会报异常
    fos.close();
    osw.close();
    bw.close();
  }
}

执行后会抛出异常:

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

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

bw.close();
//下面任意顺序
osw.close();
fos.close();

都不会报错,这是为什么呢,我们立即看看BufferedWriter的close源码:

public void close() throws IOException {
  synchronized (lock) {
  if (out == null) {
    return;
  }
  try {
    flushBuffer();
  } finally {
    out.close();
    out = null;
    cb = null;
  }
}
void flushBuffer() throws IOException {
  synchronized (lock) {
    ensureOpen();
    if (nextChar == 0)
    return;
    out.write(cb, 0, nextChar);
    nextChar = 0;
  }
}

里面调用了flushBuffer()方法,也是抛异常中的错误方法,这行out.write(cb, 0, nextChar);如果在流关闭后执行就会抛IO异常。

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

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值