NIO與IO的區別

JAVA NIO vs IO

當我們學習了Java NIO和IO後,我們很快就會思考一個問題:

什麼時候應該使用IO,什麼時候我應該使用NIO

在下文中我會嘗試用例子闡述java NIO 和IO的區別,以及它們對你的設計會有什麼影響

Java NIO和IO的主要區別

IO NIO
面向Stream 面向Buffer
阻塞IO 非阻塞IO
  Selectors


面向Stream和麪向Buffer

Java NIO和IO之間最大的區別是IO是面向流(Stream)的,NIO是面向塊(buffer)的,所以,這意味着什麼?

面向流意味着從流中一次可以讀取一個或多個字節,拿到讀取的這些做什麼你說了算,這裏沒有任何緩存(這裏指的是使用流沒有任何緩存,接收或者發送的數據是緩存到操作系統中的,流就像一根水管從操作系統的緩存中讀取數據)而且只能順序從流中讀取數據,如果需要跳過一些字節或者再讀取已經讀過的字節,你必須將從流中讀取的數據先緩存起來

面向塊的處理方式有些不同,數據是先被 讀/寫到buffer中的,根據需要你可以控制讀取什麼位置的數據。這在處理的過程中給用戶多了一些靈活性然而,你需要額外做的工作是檢查你需要的數據是否已經全部到了buffer中,你還需要保證當有更多的數據進入buffer中時,buffer中未處理的數據不會被覆蓋

阻塞IO和非阻塞IO

所有的Java IO流都是阻塞的,這意味着,當一條線程執行read()或者write()方法時,這條線程會一直阻塞知道讀取到了一些數據或者要寫出去的數據已經全部寫出,在這期間這條線程不能做任何其他的事情

java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存儲數據外和IO基本沒有區別)允許一條線程從channel中讀取數據,通過返回值來判斷buffer中是否有數據,如果沒有數據,NIO不會阻塞,因爲不阻塞這條線程就可以去做其他的事情,過一段時間再回來判斷一下有沒有數據

NIO的寫也是一樣的,一條線程將buffer中的數據寫入channel,它不會等待數據全部寫完纔會返回,而是調用完write()方法就會繼續向下執行

Selectors

Java NIO的selectors允許一條線程去監控多個channels的輸入,你可以向一個selector上註冊多個channel,然後調用selector的select()方法判斷是否有新的連接進來或者已經在selector上註冊時channel是否有數據進入。selector的機制讓一個線程管理多個channel變得簡單。


NIO和IO對應用的設計有何影響

選擇使用NIO還是IO做你的IO工具對應用主要有以下幾個方面的影響

1、使用IO和NIO的API是不同的(廢話)

2、處理數據的方式

3、處理數據所用到的線程數

處理數據的方式

在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()阻塞知道一整行都被讀取

NIO的實現會有一些不同,下面是一個簡單的例子

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);
注意第二行從channel中讀取數據到ByteBuffer,當這個方法返回你不知道是否你需要的所有數據都被讀到buffer了,你所知道的一切就是有一些數據被讀到了buffer中,但是你並不知道具體有多少數據,這使程序的處理變得稍微有些困難

想象一下,調用了read(buffer)方法後,只有半行數據被讀進了buffer,例如:“Name: An”,你能現在就處理數據嗎?當然不能。你需要等待直到至少一整行數據被讀到buffer中,在這之前確保程序不要處理buffer中的數據

你如何知道buffer中是否有足夠的數據可以被處理呢?你不知道,唯一的方法就是檢查buffer中的數據。可能你會進行幾次無效的檢查(檢查了幾次數據都不夠進行處理),這會令程序設計變得比較混亂複雜

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

while(! bufferFull(bytesRead) ) {
    bytesRead = inChannel.read(buffer);
}
bufferFull方法負責檢查有多少數據被讀到了buffer中,根據返回值是true還是false來判斷數據是否夠進行處理。bufferFull方法掃描buffer但不能改變buffer的內部狀態

is-data-in-buffer-ready 循環柱狀圖如下


總結

NIO允許你用一個單獨的線程或幾個線程管理很多個channels(網絡的或者文件的),代價是程序的處理和處理IO相比更加複雜

如果你需要同時管理成千上萬的連接,但是每個連接只發送少量數據,例如一個聊天服務器,用NIO實現會更好一些,相似的,如果你需要保持很多個到其他電腦的連接,例如P2P網絡,用一個單獨的線程來管理所有出口連接是比較合適的



如果你只有少量的連接但是每個連接都佔有很高的帶寬,同時發送很多數據,傳統的IO會更適合







發佈了27 篇原創文章 · 獲贊 29 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章