目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----彙集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----文件通道和網絡通道
Java NIO 學習筆記(五)----路徑、文件和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----異步文件通道 AsynchronousFileChannel
Java NIO 學習筆記(七)----NIO/IO 的對比和總結html
學完 NIO 和 IO 後,有一個問題:何時應該使用 IO,何時應該使用 NIO ?本文將嘗試闡明 NIO 和 IO 之間的差別,並提供它們的用例,以及它們對程序代碼的設計影響。java
IO | NIO |
---|---|
以 Stream 爲導向 | 以 Buffer 爲導向 |
阻塞 IO | 非阻塞 IO 選擇器 |
NIO 和 IO 之間的第一個重要區別是 IO 是面向流的,其中 NIO 是面向緩衝區的。 那麼,這意味着什麼?api
面向流的 IO 意味着能夠從流中一次讀取一個或多個字節,能夠按咱們的意願使用讀取的字節。 它們不會緩存在任何地方,此外,沒法在流中的將數據先後移動。 若是須要將讀取的數據先後移動,則須要先將其緩存在緩衝區中。緩存
NIO 的面向緩衝區的方法略有不一樣。 將數據讀入緩衝區,稍後處理該緩衝區。 能夠根據須要在緩衝區中先後移動。 這使在處理過程當中更具靈活性。 可是,還需檢查該緩衝區中是否包含全部須要處理的數據,而且須要確保在將更多數據讀入緩衝區時,不會覆蓋還沒有處理的緩衝區中的數據。服務器
標準 IO 的各類流都是阻塞的。 這意味着當線程調用 read() 或 write () 時,該線程將被阻塞,直到一些數據被讀取或者徹底寫入,在此期間,線程沒法執行任何其餘操做。網絡
NIO 的非阻塞模式容許線程請求從通道讀取數據,而且只獲取當前可用的內容,若是當前沒有數據可用,就什麼都不讀取。 線程能夠繼續作其餘事情,而不是在數據可供讀取以前保持阻塞狀態。
非阻塞寫入也是如此。 線程能夠請求將某些數據寫入通道,但在徹底寫入以前不會一直等待它,這樣,線程能夠在同一時間作繼續其餘事情。異步
線程在 IO 操做中沒有由於阻塞花費等待時間,一般將等待數據準備的時間用在其餘通道上執行 IO 操做。 也就是說,單個線程如今能夠管理多個輸入和輸出通道。學習
選擇器容許單個線程監視多個輸入通道。可使用選擇器註冊多個通道,而後使用單個線程「選擇」具備可用於處理的輸入的通道,或選擇準備寫入的通道。 這種選擇器機制使單個線程能夠輕鬆管理多個通道。線程
不管選擇 NIO 仍是 IO ,可能都會影響應用程序設計的如下方面:設計
固然,使用 NIO 時的 API 調用看起來與使用 IO 時不一樣。由於必須首先將數據從通道讀入緩衝區,而後在緩衝區進行處理,而不是僅僅從 InputStream 讀取數據字節。
使用純 NIO 設計是,對比 IO 設計,數據處理也會受到影響。
在 IO 設計中,從 InputStream 或 Reader 中讀取字節的數據字節。 想象一下,正在處理基於行的文本數據流。 例如:
Name: czwbig Age: 21
這組文本行能夠像這樣處理:
InputStream input = ... ; BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine();
注意處理狀態是如何根據程序執行的程度肯定的。 換句話說,一旦第一個 reader.readLine() 方法返回,就肯定已經讀取了整行文本,由於 readLine() 阻塞直到讀取完整行,還知道此行包含「Name」。 一樣,當第二個 readLine() 調用返回時,能夠知道此行包含「Age」等。
因此,只有當有新數據要讀取時,程序纔會進行,而且對於每一個步驟,都知道該讀取的數據是什麼。 一旦執行的線程已經讀取過代碼中的某個數據片斷,該線程就不會再向後讀取舊數據(一般不會)。 下圖也說明了此原則:
同上需求,NIO 實現看起來會有所不一樣。這裏有一個簡化的例子:
ByteBuffer buffer = ByteBuffer.allocate(64); int bytesRead = inChannel.read(buffer);
注意第二行從通道讀取字節到 ByteBuffer 。 當該方法調用返回時,咱們是不知道所需的全部數據是否都已在緩衝區內的,只知道緩衝區包含一些字節。 這使得處理數據變得困難。
想象一下,在第一次讀取(緩衝)調用以後,是否全部讀入緩衝區的內容都是半行。 例如,「Name:cz」。 你能處理這些數據嗎? 顯然不能。 在處理任何數據以前,咱們須要等待至少一整行數據進入緩衝區。
那麼怎麼知道緩衝區是否包含足夠的數據來處理它?惟一方法是查看緩衝區中的數據。 這樣將致使:在知道全部數據是否存在以前,可能須要屢次檢查緩衝區中的數據(輪詢)。 這既低效又可能在程序設計方面變得混亂。 例如:
ByteBuffer buffer = ByteBuffer.allocate(64); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
bufferFull() 方法必須跟蹤讀入緩衝區的數據量,並返回 true 或 false ,具體取決於緩衝區是否已滿。 換句話說,若是緩衝區已準備好進行處理,則認爲它已滿。
bufferFull() 方法掃描緩衝區,而且必須使緩衝區保持與調用 bufferFull() 方法以前相同的狀態。 若是不這樣,則可能沒法在正確的位置繼續讀入下一個數據到緩衝區中。 這不是不可能的,但這是另外一個須要注意的問題。
若是緩衝區已滿,則能夠對其進行處理。 若是緩衝區還沒滿,有可能讓程序先部分處理已到達的數據,這在的特定狀況下是有意義的。 但在許多狀況下,不完整的數據沒有處理的意義。
這個圖中說明了 is-data-in-buffer-ready 循環:
NIO 容許僅使用一個(或幾個)線程來管理多個通道(網絡鏈接或文件),但成本是解析數據可能比從阻塞流中讀取數據時更復雜一些。
若是須要同時管理數千個打開的鏈接,每一個只發送一些數據,例如聊天服務器,這在 NIO 中實現服務器多是一個優點。 一樣,若是須要與其餘計算機保持大量開放鏈接,例如,在 P2P 網絡中,使用單個線程來管理全部出站鏈接多是一個優點。 下圖中說明了這種一個線程,多個鏈接的設計:
但若是擁有較少帶寬的鏈接,一次鏈接的數據量較大,那麼經典的 IO 服務器實現可能更合適的。 下圖說明了這種典型的 IO 服務器設計:
因此,應該根據具體的狀況分析,選擇更適合的,而不是更新的。