面試(Java之IO與NIO)

1、概念

     NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但實現方式不一樣,NIO主要用到的是塊,因此NIO的效率要比IO高不少。在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另外一套就是網絡編程NIO。編程

2、NIO和IO的主要區別

下表總結了Java IO和NIO之間的主要區別:設計模式

IO數組

NIO緩存

面向流服務器

面向緩衝網絡

阻塞IOsocket

非阻塞IO工具

性能

選擇器編碼

 

 

 

 

 

 

 

 

一、面向流與面向緩衝

      Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。

Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。

    Java NIO面向緩衝區的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。

二、阻塞與非阻塞IO

      Java IO的各類流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。

Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。 線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。

三、選擇器(Selectors)

     Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

3、NIO和IO如何影響應用程序的設計

不管您選擇IO或NIO工具箱,可能會影響您應用程序設計的如下幾個方面:

1.對NIO或IO類的API調用。
2.數據處理。
3.用來處理數據的線程數。

一、API調用

固然,使用NIO的API調用時看起來與使用IO時有所不一樣,但這並不意外,由於並非僅從一個InputStream逐字節讀取,而是數據必須先讀入緩衝區再處理。

二、數據處理

使用純粹的NIO設計相較IO設計,數據處理也受到影響。

在IO設計中,咱們從InputStream或 Reader逐字節讀取數據。假設你正在處理一基於行的文本數據流,例如:

Name: Anna

Age: 25

Email: anna@mailserver.com

Phone: 1234567890

該文本行的流能夠這樣處理:

InputStream input = ... ; // get the InputStream from the client socket  

 

BufferedReader reader = new BufferedReader(new InputStreamReader(input));  

 

String nameLine   = reader.readLine();

String ageLine    = reader.readLine();

String emailLine  = reader.readLine();

String phoneLine  = reader.readLine();

 

 

     請注意處理狀態由程序執行多久決定。換句話說,一旦reader.readLine()方法返回,你就知道確定文本行就已讀完, readline()阻塞直到整行讀完,這就是緣由。你也知道此行包含名稱;一樣,第二個readline()調用返回的時候,你知道這行包含年齡等。 正如你能夠看到,該處理程序僅在有新數據讀入時運行,並知道每步的數據是什麼。一旦正在運行的線程已處理過讀入的某些數據,該線程不會再回退數據(大多如此)。下圖也說明了這條原則:

 

而一個NIO的實現會有所不一樣,下面是一個簡單的例子:

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

注意第二行,從通道讀取字節到ByteBuffer。當這個方法調用返回時,你不知道你所需的全部數據是否在緩衝區內。你所知道的是,該緩衝區包含一些字節,這使得處理有點困難。假設第一次 read(buffer)調用後,讀入緩衝區的數據只有半行,例如,「Name:An」,你能處理數據嗎?顯然不能,須要等待,直到整行數據讀入緩存,在此以前,對數據的任何處理毫無心義。因此,你怎麼知道是否該緩衝區包含足夠的數據能夠處理呢?好了,你不知道。發現的方法只能查看緩衝區中的數據。其結果是,在你知道全部數據都在緩衝區裏以前,你必須檢查幾回緩衝區的數據。這不只效率低下,並且可使程序設計方案雜亂不堪。例如

ByteBuffer buffer = ByteBuffer.allocate(48);  

 

int bytesRead = inChannel.read(buffer);  

 

while(! bufferFull(bytesRead) ) {  

       bytesRead = inChannel.read(buffer);  

}

 

bufferFull()方法必須跟蹤有多少數據讀入緩衝區,並返回真或假,這取決於緩衝區是否已滿。換句話說,若是緩衝區準備好被處理,那麼表示緩衝區滿了。

bufferFull()方法掃描緩衝區,但必須保持在bufferFull()方法被調用以前狀態相同。若是沒有,下一個讀入緩衝區的數據可能沒法讀到正確的位置。這是不可能的,但倒是須要注意的又一問題。

若是緩衝區已滿,它能夠被處理。若是它不滿,而且在你的實際案例中有意義,你或許能處理其中的部分數據。可是許多狀況下並不是如此。下圖展現了「緩衝區數據循環就緒」:

 

4、IO與NIO的選擇

NIO可以讓您只使用一個(或幾個)單線程管理多個通道(網絡鏈接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。

若是須要管理同時打開的成千上萬個鏈接,這些鏈接每次只是發送少許的數據,例如聊天服務器,實現NIO的服務器多是一個優點。一樣,若是你須要維持許多打開的鏈接到其餘計算機上,如P2P網絡中,使用一個單獨的線程來管理你全部出站鏈接,多是一個優點。一個線程多個鏈接的設計方案以下圖所示:

 

Java NIO: 單線程管理多個鏈接

若是你有少許的鏈接使用很是高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能很是契合。下圖說明了一個典型的IO服務器設計:

 

Java IO: 一個典型的IO服務器設計- 一個鏈接經過一個線程處理.

PS.

1.   Java IO 流中涉及到了哪些設計策略和設計模式

Java 的 IO 庫提供了一種連接(Chaining)機制,能夠將一個流處理器跟另外一個流處理器首尾相接,以其中之一的輸出做爲另外一個的輸入而造成一個流管道連接,譬如常見的 new DataInputStream(new FileInputStream(file)) 就是把 FileInputStream 流看成 DataInputStream 流的管道連接。其次,對於 Java IO 流還涉及一種對稱性的設計策略,其表現爲輸入輸出對稱性(如 InputStream 和 OutputStream 的字節輸入輸出操做,Reader 和 Writer 的字符輸入輸出操做)和字節字符的對稱性(InputStream 和 Reader 的字節字符輸入操做,OutputStream 和 Writer 的字節字符輸出操做)。此外,對於 Java IO 流在總體設計上還涉及裝飾者(Decorator)和適配器(Adapter)兩種設計模式。

對於 IO 流涉及的裝飾者設計模式例子以下:

//把InputStreamReader裝飾成BufferedReader來成爲具有緩衝能力的Reader。BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

對於 IO 流涉及的適配器設計模式例子以下:

//把FileInputStream文件字節流適配成InputStreamReader字符流來操做文件字符串。

FileInputStream fileInput = new FileInputStream(file);

InputStreamReader inputStreamReader = new InputStreamReader(fileInput);

而對於上面涉及的兩種設計模式通俗總結以下。裝飾者模式就是給一個對象增長一些新的功能,並且是動態的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例(各類字符流間裝飾,各類字節流間裝飾)。適配器模式就是將某個類的接口轉換成咱們指望的另外一個接口表示,目的是消除因爲接口不匹配所形成的類的兼容性問題(字符流與字節流間互相適配)。

 

 

2.   字節流與字符流有什麼區別:

計算機中的一切最終都是以二進制字節形式存在的,對於咱們常常操做的字符串,在寫入時其實都是先獲得了其對應的字節,而後將字節寫入到輸出流,在讀取時其實都是先讀到的是字節,而後將字節直接使用或者轉換爲字符給咱們使用。因爲對於字節和字符兩種操做的需求比較普遍,因此 Java 專門提供了字符流與字節流相關IO類。對於程序運行的底層設備來講永遠都只接受字節數據,因此當咱們往設備寫數據時不管是字節仍是字符最終都是寫的字節流。字符流是字節流的包裝類,因此當咱們將字符流向字節流轉換時要注意編碼問題(由於字符串轉成字節數組的實質是轉成該字符串的某種字節編碼)。字符流和字節流的使用很是類似,可是實際上字節流的操做不會通過緩衝區(內存)而是直接操做文本自己的,而字符流的操做會先通過緩衝區(內存)而後經過緩衝區再操做文件。

字符流和字節流的使用很是類似,可是實際上字節流的操做不會通過緩衝區(內存)而是直接操做文本自己的,而字符流的操做會先通過緩衝區(內存)而後經過緩衝區再操做文件。

3.  字節流和字符流哪一個好,如何選擇?

緩大多數狀況下使用字節流會更好,由於字節流是字符流的包裝,而大多數時候 IO 操做都是直接操做磁盤文件,因此這些流在傳輸時都是以字節的方式進行的(圖片等都是按字節存儲的)。

而若是對於操做須要經過 IO 在內存中頻繁處理字符串的狀況使用字符流會好些,由於字符流具有緩衝區,提升了性能。

相關文章
相關標籤/搜索