您的位置:首页 > 编程语言 > Java开发

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

2017-07-04 17:51 197 查看
前几天看了一篇文章(见参考文章),自己动手试了下,发现有些不一样结论,作博客记录下,本文主要研究两个问题:
包装流的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();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

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


先看BufferedOutputStream源代码:
public class BufferedOutputStream extends FilterOutputStream { ...
1
1

可以看到它继承FilterOutputStream,并且没有重写close方法, 

所以直接看FilterOutputStream的源代码:
public void close() throws IOException {
try {
flush();
} catch (IOException ignored) {
}
out.close();
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7

跟踪out(FilterOutputStream中):
protected OutputStream out;

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

再看看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];
}
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11

可以看到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
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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


关闭流方法是否有顺序?

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

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

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();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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();

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上述两种写法都没有问题,我们已经知道
bufferedOutputStream.close();
会自动调用
fileOutputStream.close();
方法,那么这个方法是怎么执行的呢?我们又看看FileOutputStream的源码:
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}

...
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

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

我们看下下面的代码(修改自参考文章):
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();

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

会抛出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)
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

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

都不会报错,这是为什么呢,我们立即看看BufferedWriter的close源码:
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try {
flushBuffer();
} finally {
out.close();
out = null;
cb = null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14

里面调用了flushBuffer()方法,也是抛异常中的错误方法:
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

可以看到很大的一行
out.write(cb, 0, nextChar);
1
1

这行如果在流关闭后执行就会抛IO异常, 

有时候我们会写成:
fos.close();
fos = null;
osw.close();
osw = null;
bw.close();
bw = null;
1
2
3
4
5
6
1
2
3
4
5
6

这样也会抛异常,不过是由于
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; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如何防止这种情况? 

直接写下面这种形式就可以:
bw.close();
bw = null;
1
2
1
2

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

由上述的两个结论可以得出下面的建议: 

关闭流只需要关闭最外层的包装流,其他流会自动调用关闭,这样可以保证不会抛异常。如:
bw.close();
//下面三个无顺序
osw = null;
fos = null;
bw = null;
1
2
3
4
5
1
2
3
4
5

注意的是,有些方法中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;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14

finally中就有把out置为null的代码,所以有时候不需要自己手动置为null。(个人建议还是写一下,不差多少执行时间)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: