我们来说说如何使用 Lambda 表达式实现排序功能
包装流的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;}}}
通过观察各种流的源代码,可得结论:包装的流都会自动调用被包装的流的关闭方法,无需自己调用。
关闭流方法是否有顺序?
由上面的结论,就会产生一个问题:如果手动关闭被包装流会怎么样,这个关闭流有顺序吗?而实际上习惯都是两个流都关闭的。
首先来做一个简单的实验,基于第一个问题的代码上增加手动增加关闭流的代码,那么就有两种顺序:
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 closedat 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;
:::success 结论:一个流上的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。(建议还是写一下)
- 点赞
- 收藏
- 关注作者
评论(0)