1. 程式人生 > >NIO與IO的區別

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會更適合