Java IO流关闭问题的深入研究
转自:https://blog.csdn.net/maxwell_nc/article/details/49151005
前几天看了一篇文章(见参考文章),自己动手试了下,发现有些不一样结论,作博客记录下,本文主要研究两个问题:
包装流的close方法是否会自动关闭被包装的流?
关闭流方法是否有顺序?
包装流的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 { ...
1
可以看到它继承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;
}
}
}
通过观察各种流的源代码,可得结论:包装的流都会自动调用被包装的流的关闭方法,无需自己调用。
关闭流方法是否有顺序?
由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上我们习惯都是两个流都关闭的。
首先我们来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:
1.先关闭被包装流(正常没异常抛出)
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(); fileOutputStream.close();//先关闭被包装流
bufferedOutputStream.close();
} }
2.先关闭包装流(正常没异常抛出)
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();//先关闭包装流
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(); } }
会抛出Stream closed的IO异常:
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();
bw.close();
fos.close();
osw.close();
都不会报错,这是为什么呢,我们立即看看BufferedWriter的close源码:
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try {
flushBuffer();
} finally {
out.close();
out = null;
cb = null;
}
}
}
里面调用了flushBuffer()方法,也是抛异常中的错误方法:
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
可以看到很大的一行
out.write(cb, 0, nextChar);
这行如果在流关闭后执行就会抛IO异常,
有时候我们会写成:
fos.close();
fos = null;
osw.close();
osw = null;
bw.close();
bw = null;
这样也会抛异常,不过是由于flushBuffer()中ensureOpen()抛的,可从源码中看出:
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
}
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
如何防止这种情况?
直接写下面这种形式就可以:
bw.close();
bw = null;
结论:一个流上的close方法可以多次调用,理论上关闭流不需要考虑顺序,但有时候关闭方法中调用了write等方法时会抛异常。
由上述的两个结论可以得出下面的建议:
关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。如:
bw.close();
//下面三个无顺序
osw = null;
fos = null;
bw = null;
注意的是,有些方法中close方法除了调用被包装流的close方法外还会把包装流置为null,方便JVM回收。bw.close()中的:
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try {
flushBuffer();
} finally {
out.close();
out = null;
cb = null;
}
}
}
finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(个人建议还是写一下,不差多少执行时间)
Java IO流关闭问题的深入研究的更多相关文章
- java io流(字符流) 文件打开、读取文件、关闭文件
java io流(字符流) 文件打开 读取文件 关闭文件 //打开文件 //读取文件内容 //关闭文件 import java.io.*; public class Index{ public sta ...
- JAVA.IO流学习笔记
一.java.io 的描述 通过数据流.序列化和文件系统提供系统输入和输出.IO流用来处理设备之间的数据传输 二.流 流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数 ...
- Java IO流学习
Java IO流学习 Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是 ...
- Java:IO流与文件基础
Java:IO流与文件基础 说明: 本章内容将会持续更新,大家可以关注一下并给我提供建议,谢谢啦. 走进流 什么是流 流:从源到目的地的字节的有序序列. 在Java中,可以从其中读取一个字节序列的对象 ...
- java IO流详解
流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...
- Java IO流学习总结
Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...
- 揭开Java IO流中的flush()的神秘面纱
大家在使用Java IO流中OutputStream.PrintWriter --时,会经常用到它的flush()方法. 与在网络硬件中缓存一样,流还可以在软件中得到缓存,即直接在Java代码中缓存. ...
- Java IO流题库
一. 填空题 Java IO流可以分为 节点流 和处理流两大类,其中前者处于IO操作的第一线,所有操作必须通过他们进行. 输入流的唯一目的是提供通往数据的通道,程序可以通过这个通道读取数 ...
- java io流 创建文件、写入数据、设置输出位置
java io流 创建文件 写入数据 改变system.out.print的输出位置 //创建文件 //写入数据 //改变system.out.print的输出位置 import java.io.*; ...
随机推荐
- vue-awesome-swiper使用纪实
最近做一个项目,需要用到轮播和全屏滑动功能,所以用到了vue-awesome-swiper插件,以下为个人记录下此插件的用法. 效果说明: 上面部分是个轮播图,自动开始轮播,轮播间隔为3000ms 推 ...
- 捕获程序异常之tryCatch
一.try catch语法try…catch…finally…语法中除了try以外,catch和finally都是可选的(两者必须要有一个),也就是说try…catch…finally…语法有以下三种 ...
- [ 转载 ] Android JNI(一)——NDK与JNI基础
Android JNI(一)——NDK与JNI基础 隔壁老李头 关注 4.4 2018.05.09 17:15* 字数 5481 阅读 11468评论 8喜欢 140 本系列文章如下: Androi ...
- JFreeChart 之饼图
JFreeChart 之饼图 一.JFreeChart 简介 JFreeChart是JAVA平台上的一个开放的图表绘制类库.它完全使用JAVA语言编写,是为applications, applets, ...
- 1200 同余方程 2012年NOIP全国联赛提高组
题目描述 Description 求关于 x 同余方程 ax ≡ 1 (mod b)的最小正整数解. 输入描述 Input Description 输入只有一行,包含两个正整数 a, b,用 一个 空 ...
- Codeforces Round #396 (Div. 2) C. Mahmoud and a Message dp
C. Mahmoud and a Message 题目连接: http://codeforces.com/contest/766/problem/C Description Mahmoud wrote ...
- Windows平台开发Mapreduce程序远程调用运行在Hadoop集群—Yarn调度引擎异常
共享原因:虽然用一篇博文写问题感觉有点奢侈,但是搜索百度,相关文章太少了,苦苦探寻日志才找到解决方案. 遇到问题:在windows平台上开发的mapreduce程序,运行迟迟没有结果. Mapredu ...
- MikroTik RouterOS 5.x使用HunterTik 2.3.1进行破解
一.加载光驱: 二.一路回车: 三.说明: 1.可以不安装Debian内核,但如果在无缝升级到6.6的版本,此项就一定要选择. 2.6版本的破解必须小于等于1G的空间,不然无法破解成功,亲测有效,如果 ...
- CSS动画简介
现在,我很少写介绍CSS的文章,因为感觉网站开发的关键还是在服务器端. 但是,CSS动画除外,它实在太有用了. 本文介绍CSS动画的两大组成部分:transition和animation.我不打算给出 ...
- javaDoc 注释规范
Javadoc虽然是Sun公司为Java文档自动生成设计的,可以从程序源代码中抽取类.方法.成员等注释形成一个和源代码配套的API帮助文档.但是Javadoc的注释也符合C的注释格式,而且doxyen ...