轉自: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。(個人建議還是寫一下,不差多少執行時間)