1. 程式人生 > >對IO流關閉的思考

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