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

原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/10132540.html

时间: 2024-11-11 03:22:29

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

java io流(字符流) 文件打开、读取文件、关闭文件

java io流(字符流) 文件打开 读取文件 关闭文件 //打开文件 //读取文件内容 //关闭文件 import java.io.*; public class Index{ public static void main(String[] args) throws Exception{ //打开文件 //字符流方式打开 //字符流每次按一个字符读取 FileReader wj = new FileReader("D:/java/kj/javanew/src/Index.java"

Java:IO流与文件基础

Java:IO流与文件基础 说明: 本文所有内容包含图片均为MrSaber自己编写,转载请练习我哦. 本章内容将会持续更新,大家可以关注一下并给我提供建议,谢谢啦. 走进流 什么是流 流:从源到目的地的字节的有序序列. 在Java中,可以从其中读取一个字节序列的对象称作 输入流,可以向其中写入一个字节序列的对象称作 输出流. ? 这些字节序列的来源可以是:文件.网络连接.内存块等. ? 抽象类InputStream和OutputStream是构成输入/输出(I/O)的基础. ? 因为面向字节的流

java io流(字节流)复制文件

java io流(字节流) 复制文件 //复制文件 //使用字节流 //复制文本文件用字符流,复制其它格式文件用字节流 import java.io.*; public class Index{ public static void main(String[] args) throws Exception{ //字符流方式 //FileReader fz = new FileReader("E:/1.txt"); //FileWriter zt = new FileWriter(&qu

Java IO流学习总结

Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作. IO流的分类 根据处理数据类型的不同分为:字符流和字节流 根据数据流向不同分为:输入流和输出流 字符流和字节流 字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象.本质其实就是基于字节流读取时,去查了指定的码表. 字节流和字符流的区

Java IO流学习总结(转)

原文地址:http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作.   IO流的分类 根据处理数据类型的不同分为:字符流和字节流 根据数据流向不同分为:输入流和输出流 字符流和字节流 字符

揭开Java IO流中的flush()的神秘面纱

大家在使用Java IO流中OutputStream.PrintWriter --时,会经常用到它的flush()方法. 与在网络硬件中缓存一样,流还可以在软件中得到缓存,即直接在Java代码中缓存.这可以通过BufferedOutputStream或BufferedWriter 链接到底层流上来实现. 因此,在写完数据时,flush就显得尤为重要. 例如: 上图中WEB服务器通过输出流向客户端响应了一个300字节的信息,但是,这时的输出流有一个1024字节的缓冲区.所以,输出流就一直等着WEB

java io流 创建文件、写入数据、设置输出位置

java io流 创建文件 写入数据 改变system.out.print的输出位置 //创建文件 //写入数据 //改变system.out.print的输出位置 import java.io.*; public class Index{ public static void main(String[] args) throws Exception{ /** * 存储为二进制,给计算机看的 */ //创建文件 DataOutputStream sjl = new DataOutputStrea

Java IO流详尽解析(转自 http://www.2cto.com/kf/201312/262036.html)

流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作. IO流的分类 根据处理数据类型的不同分为:字符流和字节流 根据数据流向不同分为:输入流和输出流 字符流和字节流 字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象.本质其实就是基于字节流读取时,去查了指定的码表.字节流和字符流的区别

黑马程序员-Java IO流

--Java培训.Android培训.iOS培训..Net培训.期待与您交流!-- 一.概述 Java中与IO相关的类有很多,都集中在java.io中,都是以流的形式操作的,流是有一定的顺序,像一个管道一样,它的本质是传输数据.根据数据类型的不同可以分为字节流和字符流,根据流向的不同可以分为输入流和输出流. 字符流:因为数据有不同的编码,可以对字符进行不同的操作,其本质还是基于字节流,然后再查询相应的码表.一般用于处理纯文本数据. 字节流:可以处理所有类型数据,二进制文件(图片,音频等). 输入