前幾天看了一篇文章,自己動手試了下,發現有些不一樣結論,作博客記錄下,本文主要研究兩個問題: 包裝流的close方法是否會自動關閉被包裝的流? 關閉流方法是否有順序? 包裝流的close方法是否會自動關閉被包裝的流? 平時我們使用輸入流和輸出流一般都會使用buffer包裝一下,直接看下麵代碼(這個代 ...
前幾天看了一篇文章,自己動手試了下,發現有些不一樣結論,作博客記錄下,本文主要研究兩個問題:
- 包裝流的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 { ...
可以看到它繼承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。(個人建議還是寫一下,不差多少執行時間)
來源:blog.csdn.net/maxwell_nc/article/details/49151005
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!