Java——IO流

gary-liu發表於2017-02-18

流的理解

在很多時候,流(Stream)是位元組流(Byte Steram)的簡稱,也就是長長的一串位元組,當然,除了位元組流,我們還可以有視訊流、音訊流、資料流。流只有一個特徵就是連續,流可以沒有頭可以沒有尾,甚至可能沒有絕對的位置(因為無頭無尾),但是由於流是連續的,所以有相對位置。

但也有人更傾向於流是類庫或執行環境中的資料流概念,而不是程式語言裡的概念。這種“流”的典型代表好比Java裡的InputStream,OutputStream。而說起程式語言裡的Stream,應該說是一種“延遲執行”的序列構造,典型代表是Java 8裡 java.util.stream 的那些東西。

參見:https://www.zhihu.com/question/27996269/answer/38960397

流有哪些分類?

可以從不同的角度對流進行分類:

  1. 處理的資料單位不同,可分為:字元流,位元組流
  2. 資料流方向不同,可分為:輸入流,輸出流
  3. 功能不同,可分為:節點流,處理流

1 和 2 都比較好理解,對於根據功能分類的,可以這麼理解。

節點流:節點流從一個特定的資料來源讀寫資料。即節點流是直接操作檔案,網路等的流,例如FileInputStream和FileOutputStream,他們直接從檔案中讀取或往檔案中寫入位元組流。

處理流:“連線”在已存在的流(節點流或處理流)之上通過對資料的處理為程式提供更為強大的讀寫功能。過濾流是使用一個已經存在的輸入流或輸出流連線建立的,過濾流就是對節點流進行一系列的包裝。例如BufferedInputStream和BufferedOutputStream,使用已經存在的節點流來構造,提供帶緩衝的讀寫,提高了讀寫的效率,以及DataInputStream和DataOutputStream,使用已經存在的節點流來構造,提供了讀寫Java中的基本資料型別的功能。他們都屬於過濾流。

流結構介紹

這裡寫圖片描述

參見文章: http://www.cnblogs.com/shitouer/archive/2012/12/19/2823641.html

為什麼使用緩衝區

FileOutPutStream 繼承 OutputStream,並不提供 flush() 方法的重寫所以無論內容多少write都會將二進位制流直接傳遞給底層作業系統的I/O,flush無效果。而 Buffered 系列的輸入輸出流函式單從Buffered這個單詞就可以看出他們是使用緩衝區的。應用程式每次IO都要和裝置進行通訊,效率很低,因此緩衝區為了提高效率,當寫入裝置時,先寫入緩衝區,每次等到緩衝區滿了時,就將資料一次性整體寫入裝置,避免了每一個資料都和IO進行一次互動,IO互動消耗太大。

什麼時候使用flush()

 對於輸出的緩衝流,寫出的資料,會先寫入到記憶體中,再使用flush方法將記憶體中的資料刷到硬碟。所以,在使用字元緩衝流的時候,一定要先flush,然後再close,避免資料丟失。jdk原始碼中有的類中的close方法會呼叫flush的,所以也可不需要先flush;但有的時候需要手動的呼叫flush, 比如 socket 套接字。
 

預設緩衝區大小

8192個位元組。
可以去jdk中檢視 BufferedReader 類的原始碼,裡面定義了 private static int defaultCharBufferSize = 8192 即8K。

使用flush()和不使用flush()效果對比參見文章:https://segmentfault.com/a/1190000003804439

為什麼關閉流

java本身是帶GC的,所以物件在消除引用之後,按正常是能夠被回收的,那麼為什麼會有關閉操作?
這是為了回收系統資源,主要是埠(網路IO),檔案控制程式碼(輸入輸出)等,通常涉及的場景也是操作檔案,網路操作、資料庫應用等。對於類unix系統,所有東西都是抽象成檔案的,所以可以通過lsof來觀察。

在Java中如果執行過多的流操作或者開啟過多未關閉的Socket,並且沒有及時的關閉,就可能會出現too many open files 的錯誤。這就是因為系統的檔案控制程式碼數不夠了. 檢視檔案控制程式碼數 ulimit -n, 修改檔案控制程式碼數 ulimit -n 2048

參見文章從流關閉說起:http://mccxj.github.io/blog/20130821_java-hell-stream-close.html

流的關閉順序

一般情況下是:先開啟的後關閉,後開啟的先關閉
另一種情況:看依賴關係,如果流a依賴流b,應該先關閉流a,再關閉流b
例如處理流a依賴節點流b,應該先關閉處理流a,再關閉節點流b
當然完全可以只關閉處理流,不用關閉節點流。處理流關閉的時候,會呼叫其處理的節點流的關閉方法,比如看BufferedInputStream
類的close方法的實現
如果將節點流關閉以後再關閉處理流,會丟擲IO異常;

建議:
關閉流只需要關閉最外層的包裝流,其他流會自動呼叫關閉,這樣可以保證不會拋異常。如:

bw.close();      //最外層包裝流
//下面三個無順序
osw = null;
fos = null;
bw = null;

注意的是,有些方法中close方法除了呼叫被包裝流的close方法外還會把包裝流置為null,方便JVM回收。

也可以使用 Try-with-resources

 try(FileInputStream input = new FileInputStream("file.txt"); BufferedInputStream bufferedInput = new BufferedInputStream(input)
        ) {

            int data = bufferedInput.read();
            while(data != -1){
                System.out.print((char) data);
        data = bufferedInput.read();
            }
        }

上面的例子在try關鍵字後的括號裡建立了兩個資源——FileInputStream 和BufferedInputStream。當程式執行離開try語句塊時,這兩個資源都會被自動關閉。
這些資源將按照他們被建立順序的逆序來關閉。首先BufferedInputStream 會被關閉,然後FileInputStream會被關閉。

參考文章
Java IO包裝流如何關閉? : http://www.cnblogs.com/qqzy168/p/3670915.html (不完善)
Java IO流關閉問題的深入研究: http://blog.csdn.net/maxwell_nc/article/details/49151005 (對上面有補充)

IO流使用的設計模式

IO流主要使用了裝飾模式

阻塞非阻塞與同步非同步

在IO中經常聽到這些名詞,這裡引用個不錯的解釋。

  • 同步與非同步

同步和非同步關注的是訊息通訊機制 (synchronous communication/ asynchronous communication)
所謂同步,就是在發出一個呼叫時,在沒有得到結果之前,該呼叫就不返回。但是一旦呼叫返回,就得到返回值了。
換句話說,就是由呼叫者主動等待這個呼叫的結果。

而非同步則是相反,呼叫在發出之後,這個呼叫就直接返回了,所以沒有返回結果。換句話說,當一個非同步過程呼叫發出後,呼叫者不會立刻得到結果。而是在呼叫發出後,被呼叫者通過狀態、通知來通知呼叫者,或通過回撥函式處理這個呼叫。

典型的非同步程式設計模型比如Node.js

舉個通俗的例子:
你打電話問書店老闆有沒有《分散式系統》這本書,如果是同步通訊機制,書店老闆會說,你稍等,”我查一下”,然後開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。
而非同步通訊機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,然後直接掛電話了(不返回結果)。然後查好了,他會主動打電話給你。在這裡老闆通過“回電”這種方式來回撥。

  • 阻塞與非阻塞

阻塞和非阻塞關注的是程式在等待呼叫結果(訊息,返回值)時的狀態.

阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。呼叫執行緒只有在得到結果之後才會返回。
非阻塞呼叫指在不能立刻得到結果之前,該呼叫不會阻塞當前執行緒。

還是上面的例子,
你打電話問書店老闆有沒有《分散式系統》這本書,你如果是阻塞式呼叫,你會一直把自己“掛起”,直到得到這本書有沒有的結果,如果是非阻塞式呼叫,你不管老闆有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。
在這裡阻塞與非阻塞與是否同步非同步無關。跟老闆通過什麼方式回答你結果無關。

參見文章:https://www.zhihu.com/question/19732473

相關文章