zoukankan      html  css  js  c++  java
  • Java IO流关闭问题的深入研究

    转自:https://blog.csdn.net/maxwell_nc/article/details/49151005

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

    包装流的close方法是否会自动关闭被包装的流?
    关闭流方法是否有顺序?
    包装流的close方法是否会自动关闭被包装的流?
    平时我们使用输入流和输出流一般都会使用buffer包装一下,
    直接看下面代码(这个代码运行正常,不会报错)

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

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

    先看BufferedOutputStream源代码:

    public class BufferedOutputStream extends FilterOutputStream { ...
    1
    可以看到它继承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 
    3 public FilterOutputStream(OutputStream out) {
    4 this.out = out;
    5 }

    再看看BufferedOutputStream中:

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

    可以看到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 
     5 
     6 public class IOTest {
     7 
     8 public static void main(String[] args) throws IOException {
     9 
    10 FileOutputStream fileOutputStream = new FileOutputStream("c:\a.txt");
    11 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
    12 
    13 bufferedOutputStream.write("test write something".getBytes());
    14 bufferedOutputStream.flush();
    15 
    16 fileOutputStream.close();//先关闭被包装流
    17 bufferedOutputStream.close();
    18 }
    19 
    20 }

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

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

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

     1 FileOutputStream的源码:
     2 
     3 public void close() throws IOException {
     4 synchronized (closeLock) {
     5 if (closed) {
     6 return;
     7 }
     8 closed = true;
     9 }
    10 
    11 ...

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

    我们看下下面的代码(修改自参考文章):

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

    会抛出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();
    1 bw.close();
    2 fos.close();
    3 osw.close();

    都不会报错,这是为什么呢,我们立即看看BufferedWriter的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 }


    里面调用了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 
     6 
     7 void flushBuffer() throws IOException {
     8 synchronized (lock) {
     9 ensureOpen();
    10 if (nextChar == 0)
    11 return;
    12 out.write(cb, 0, nextChar);
    13 nextChar = 0;
    14 }
    15 }

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

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

    结论:一个流上的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。(个人建议还是写一下,不差多少执行时间)

  • 相关阅读:
    动态生成java、动态编译、动态加载
    boostrap标签
    Java后端WebSocket的Tomcat实现
    编程过程与规范
    敏捷开发过程
    软件过程模型
    软件过程
    ISO9126 质量模型
    手机淘宝架构演化实践
    蒋勋:知识分子应具备怎样的美学修养
  • 原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/10132540.html
Copyright © 2011-2022 走看看