java面試---IO與NIO

一、概念

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

二、NIO和IO的主要區別

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

IO

NIO

面向流

面向緩衝

阻塞IO

非阻塞IO

選擇器

 

 

 

 

1、面向流與面向緩衝

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

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

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

2、阻塞與非阻塞IO

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

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

3、選擇器(Selectors)

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

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

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

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

1、API調用

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

2、數據處理

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

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

Name: Anna

Age: 25

Email: [email protected]

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()調用返回的時候,你知道這行包含年齡等。 正如你可以看到,該處理程序僅在有新數據讀入時運行,並知道每步的數據是什麼。一旦正在運行的線程已處理過讀入的某些數據,該線程不會再回退數據(大多如此)。下圖也說明了這條原則:

webpuploading.4e448015.gif轉存失敗重新上傳取消

Java IO: 從一個阻塞的流中讀數據) 而一個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()方法被調用之前狀態相同。如果沒有,下一個讀入緩衝區的數據可能無法讀到正確的位置。這是不可能的,但卻是需要注意的又一問題。
如果緩衝區已滿,它可以被處理。如果它不滿,並且在你的實際案例中有意義,你或許能處理其中的部分數據。但是許多情況下並非如此。下圖展示了“緩衝區數據循環就緒”:

webpuploading.4e448015.gif轉存失敗重新上傳取消

Java NIO:從一個通道里讀數據,直到所有的數據都讀到緩衝區裏.

3) 用來處理數據的線程數

NIO可讓您只使用一個(或幾個)單線程管理多個通道(網絡連接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。
如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,實現NIO的服務器可能是一個優勢。同樣,如果你需要維持許多打開的連接到其他計算機上,如P2P網絡中,使用一個單獨的線程來管理你所有出站連接,可能是一個優勢。一個線程多個連接的設計方案如下圖所示:

 

webpuploading.4e448015.gif轉存失敗重新上傳取消

Java NIO: 單線程管理多個連接
如果你有少量的連接使用非常高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能非常契合。下圖說明了一個典型的IO服務器設計:

webpuploading.4e448015.gif轉存失敗重新上傳取消

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 在內存中頻繁處理字符串的情況使用字符流會好些,因爲字符流具備緩衝區,提高了性能。

4. 什麼是緩衝區?有什麼作用?

緩衝區就是一段特殊的內存區域,很多情況下當程序需要頻繁地操作一個資源(如文件或數據庫)則性能會很低,所以爲了提升性能就可以將一部分數據暫時讀寫到緩存區,以後直接從此區域中讀寫數據即可,這樣就顯著提升了性。
對於 Java 字符流的操作都是在緩衝區操作的,所以如果我們想在字符流操作中主動將緩衝區刷新到文件則可以使用 flush() 方法操作。

5. 什麼是Java序列化,如何實現Java序列化?

序列化就是一種用來處理對象流的機制,將對象的內容進行流化。可以對流化後的對象進行讀寫操作,可以將流化後的對象傳輸於網絡之間。序列化是爲了解決在對象流讀寫操作時所引發的問題
序列化的實現:將需要被序列化的類實現Serialize接口,沒有需要實現的方法,此接口只是爲了標註對象可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,再使用ObjectOutputStream對象的write(Object obj)方法就可以將參數obj的對象寫出

6. PrintStream、BufferedWriter、PrintWriter的比較?

è¿éåå¾çæè¿°

PrintStream類的輸出功能非常強大,通常如果需要輸出文本內容,都應該將輸出流包裝成PrintStream後進行輸出。它還提供其他兩項功能。與其他輸出流不同,PrintStream 永遠不會拋出 IOException;而是,異常情況僅設置可通過 checkError 方法測試的內部標誌。另外,爲了自動刷新,可以創建一個 PrintStream
BufferedWriter:將文本寫入字符輸出流,緩衝各個字符從而提供單個字符,數組和字符串的高效寫入。通過write()方法可以將獲取到的字符輸出,然後通過newLine()進行換行操作。BufferedWriter中的字符流必須通過調用flush方法才能將其刷出去。並且BufferedWriter只能對字符流進行操作。如果要對字節流操作,則使用BufferedInputStream
PrintWriterprintln方法自動添加換行,不會拋異常,若關心異常,需要調用checkError方法看是否有異常發生,PrintWriter構造方法可指定參數,實現自動刷新緩存(autoflush)

7. BufferedReader屬於哪種流,它主要是用來做什麼的,它裏面有那些經典的方法?

屬於處理流中的緩衝流,可以將讀取的內容存在內存裏面,有readLine()方法,它,用來讀取一行

8. 什麼是節點流,什麼是處理流,它們各有什麼用處,處理流的創建有什麼特徵?

節點流 直接與數據源相連,用於輸入或者輸出
處理流:在節點流的基礎上對之進行加工,進行一些功能的擴展
處理流的構造器必須要 傳入節點流的子類

9.流一般需要不需要關閉,如果關閉的話在用什麼方法,一般要在那個代碼塊裏面關閉比較好,處理流是怎麼關閉的,如果有多個流互相調用傳入是怎麼關閉的?

流一旦打開就必須關閉,使用close方法
放入finally語句塊中(finally 語句一定會執行)
調用的處理流就關閉處理流
多個流互相調用只關閉最外層的流

10. InputStream裏的read()返回的是什麼,read(byte[] data)是什麼意思,返回的是什麼值?

返回的是所讀取的字節的int型(範圍0-255)
read(byte [ ] data)將讀取的字節儲存在這個數組。返回的就是傳入數組參數個數

11. OutputStream裏面的write()是什麼意思,write(byte b[], int off, int len)這個方法裏面的三個參數分別是什麼意思?

write將指定字節傳入數據源
Byte b[ ]是byte數組
b[off]是傳入的第一個字符、b[off+len-1]是傳入的最後的一個字符 、len是實際長度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章