對IO流關閉的思考
流必須要關閉的原因
java相對C,C++來說不需要手動釋放記憶體,在物件引用被消除之後,正常情況下記憶體資源是會被垃圾回收,那麼在使用完IO流之後為什麼需要手動關閉.
這是為了回收系統資源,比如釋放佔用的埠,檔案控制代碼,網路操作資料庫應用等.對Unix系統來說,所有的資源都可以抽象成檔案,所以可以通過lsof來觀察。
看下面這個例子,我們建立許多的IO流但是不關閉
public class Hello {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100 ; i++) {
FileInputStream is = new FileInputStream("/tmp/data/data"+i);
byte[] bs = new byte[512];
int len;
while ((len = is.read(bs)) != -1) ;
Thread.sleep(1000L);
}
Thread.sleep(10000L);
}
}
執行這個程式之後,使用losf命令檢視相關的檔案控制代碼,會發現檔案控制代碼始終在增長,當積累到一定時間之後會出現too many open files錯誤
如何關閉流
在java7以前流的關閉比較繁瑣,我們使用try-finally塊進行關閉操作,同時還要考慮流關閉過程中可能的異常
InputStream is = null;
OutputStream os = null;
try{
// ...
}
finally{
if(is != null)
try{
is.close();
}
catch(IOException e){}
if(os != null)
try{
os.close()
}
catch(IOException e){}
}
在java7以後,我們可以使用try-with-resources,將需要關閉的流物件放在try的()中建立,需要注意的是隻有實現Closeable介面的物件才可以放在這裡建立
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));) {
String s;
while ((s = reader.readLine()) != null) {
if (s.equalsIgnoreCase("quit")){
break;
}
System.out.println(s.toUpperCase());
}
} catch (Exception e) {
e.printStackTrace();
}
包裝流的關閉
包裝流關閉的時候會自動呼叫被包裝的流的關閉方法
看下面這個例子
開啟的檔案控制代碼必須要關閉,但是這個例子是例外,從控制檯中讀取字串並列印
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));) {
String s;
while ((s = reader.readLine()) != null) {
if (s.equalsIgnoreCase("quit")){
break;
}
System.out.println(s.toUpperCase());
}
} catch (Exception e) {
e.printStackTrace();
}
這裡使用了try-with-resources的寫法(後面會具體的講到)自動關閉reader物件,但是這裡需要注意,BufferedReader包裝的是System.in流,這個流是java的標準流用於接收鍵盤資料,如果這裡講System.in流關閉,那麼其它的程式可能會出錯,那麼我們究竟需要關閉哪些流呢?統一的原則是 誰建立誰銷燬,如果我們必須要在關閉不是自己建立的流物件,一定要通過文件告知介面的呼叫方
關閉包裝流與被包裝流的時候有沒有順序視情況而定
我們在使用快取輸出流的情況下,必須要優先關閉快取輸出流否則丟擲異常.原因很簡單,看下面這個例子,這裡為了方便沒有規範的關閉流
FileOutputStream fos = new FileOutputStream(filepath);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
bw.write("java IO close test");
// 從內帶外順序順序會報異常
fos.close();
osw.close();
bw.close();
快取輸出流的作用是將輸出資料先快取起來,等待快取區滿了之後一次性輸出來提高通道利用率,在呼叫快取輸出流的close方法的時候,會呼叫被裝飾物件的write方法,如果被裝飾物件被提前關閉了,那麼自然就丟擲異常
所以上面的例子修改為
bw.close();
fos.close();
osw.close();
如果沒有涉及到快取輸出流那麼就無所謂關閉順序了
FileOutputStream fileOutputStream = new FileOutputStream("c:\\a.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
bufferedOutputStream.write("test write something".getBytes());
bufferedOutputStream.flush();
fileOutputStream.close();//先關閉被包裝流
bufferedOutputStream.close();