Java NIO學習(一)NIO相關概念

Netty 與NIO

Netty在各種應用場合聽過無數次了,對它的瞭解也僅侷限於知道Netty是一個NIO的框架,可以用於開發分佈式的Java程序。網絡編程正好也是自己比較薄弱的環節,所以需要好好整理一下散落的知識點,爲學習Netty打好基礎,所以覺得有必要重新學習一下NIO。

BIO、NIO、AIO

按照以往的認知,NIO是Java4以後纔出現的一種非阻塞式IO,這是與之前存在的BIO(Blocking-IO阻塞式IO)相比,字面上最直觀的區別。除此之外,NIO最重要的特點是支持異步IO,所以也經常聽人把NIO叫做異步IO(雖然不知道這麼叫到底好不好。。。)
至於AIO,應該是Java7之後NIO的下一個形態,沒用過,在這裏就不多提啦。

阻塞&非阻塞

上面講到了BIO和NIO在性能特點上的一個重要差異,這些概念網上都很好查,所以還是用自己的話說一下這幾個概念的理解,也不知道能不能被看官所理解,如果有不恰當的地方希望能夠指正

大家都有去酒店吃飯的經歷,吃飯的時候有的時候在大堂,有的時候在包廂。

區別在哪呢?在於服務員。

非阻塞式
如果在大堂吃飯,服務員給你遞上菜單,你在點菜的這段時間裏,服務員可能就去忙別的事了,比如給其他桌的客人遞菜單,或者收拾其他桌客人離開後殘留的垃圾。點菜的過程中服務員會時不時的回來問你點好了沒有,如果沒有,服務員就繼續去忙別的事,過一會兒再回來問你。

阻塞式
如果在包廂吃飯,一般一個包廂都會配備一名專門的服務員,負責在席間斟酒和收拾餐盤裏的垃圾,除非包廂內的飯局結束,否則這名服務員就一直在包廂內爲客人服務。

上面例子裏的服務員可以看做訪問數據的進程,包廂裏的客人是阻塞式的IO操作,因爲他們還沒吃完飯,緩衝區的數據(包廂資源)還沒有被IO處理完畢(客人用餐),進程(服務員)的就必須一直等在那裏。
而大廳的客人是非阻塞式的IO操作,緩衝區的數據(點餐服務)還沒有被IO處理完畢(客人點餐),進程(服務員)可以得到一個反饋,自己可以先去忙別的事,等會再來也行。

NIO與IO

NIO和IO最大的區別是傳輸方式

IO是以的方式處理數據,而NIO是以的方式處理數據。處理的數據量擴大帶來了數據傳輸效率上的飛速提升,相對的,就沒有流傳輸的簡單性和直觀性,代碼複雜度也相應的提升了,這個會在之後的代碼實例中有所體現。

說到這個,想起dubbo的底層是通過Socket(結合apache mina框架做底層調用)來建立長連接發送、接收數據。而mina的底層正是通過NIO實現的,所以,以塊的方式處理數據在網絡通信中是很普遍的,這個會在之後dubbo的學習中有所講解。

下面進入正題,從三個基本要素來認識NIO:

  • Buffer
  • Channel
  • Selector

Buffer緩衝區

直觀的來說,Buffer你可以看做是一個數據存放容器,是我在上面說的一個個“數據塊”,對應IO中的stream。

我們可以對其進行讀寫操作,Buffer實質上是一個數組,通常是一個字節數據,但也可以是其他類型的數組。但一個緩衝區不僅僅是一個數組,重要的是它提供了對數據的結構化訪問。

常用的Buffer類型主要有:ByteBuffer、CharBuffer、IntBuffer、ShortBuffer、FloatBuffer、DoubleBuffer、LongBuffer。很好記,就是Java的八大基本類型除去Boolean。

下面以ByteBuffer爲例,簡單說一下常用方法:

聲明緩衝區-allocate(int capacity)

//接收和發送的緩衝區
private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);

清空緩衝區-clear()/compact()

//清空發生緩衝區,準備發送消息
sendBuffer.clear();

clear() 方法會清空整個緩衝區,compact() 方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。

寫緩衝區-put(byte[] src)

String sendText = "player";
sendBuffer.put(sendText.getBytes());

讀寫指針操作-flip()

sendBuffer.flip();

我們看一下flip()方法的源碼:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
 }

flip方法涉及到bufer中的Capacity、Position和Limit三個數值的操作,我們先看一下這三個數值分別代表什麼意義。

  • position:跟蹤已經寫了多少數據或讀了多少數據,它指向的是下一個字節來自哪個位置
  • limit:代表還有多少數據可以取出或還有多少空間可以寫入,它的值小於等於capacity。
  • capacity:代表緩衝區的最大容量,一般新建一個緩衝區的時候,limit的值和capacity的值默認是相等的。

其中Capacity在讀寫模式下都是固定的,就是我們分配的緩衝大小,Position類似於讀寫指針,表示當前讀(寫)到什麼位置,Limit在寫模式下表示最多能寫入多少數據,此時和Capacity相同,在讀模式下表示最多能讀多少數據,此時和緩存中的實際數據大小相同。在寫模式下調用flip方法,那麼limit就設置爲了position當前的值(即當前寫了多少數據),position會被置爲0,以表示讀操作從緩存的頭開始讀。也就是說調用flip之後,讀寫指針指到緩存頭部,並且設置了最多隻能讀出之前寫入的數據長度(而不是整個緩存的容量大小)。

到此爲止,我們把數據裝填入了緩衝區Buffer,那麼我們應該如何傳輸緩衝區的數據呢?接下來就要講到通道的概念:Channel

Channel 通道

顧名思義,channel在NIO中扮演着通道的角色,負責傳輸緩衝區Buffer中的數據塊。

打個比方,如果說Buffer是碼頭的話,碼頭中的一個個集裝箱,就是存放在Buffer中的數據塊,channel就是航道,運輸這些集裝箱,就需要走我們的航道,將集裝箱送到目的地的碼頭,具體去哪個碼頭取集裝箱,是由航道決定的,這條航道通往哪裏,你就去哪個碼頭。
所以,我們獲取集裝箱(數據塊)是去找碼頭(Buffer),找碼頭(Buffer)之前,我先需要通過航道(Channel)把數據塊讀入運入(讀入)碼頭。

private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
.
.
.
channel.read(receiveBuffer);

通過上面這個例子,可以引申出channel的一個特性:

  • >對Channel的讀寫必須通過buffer對象

除此之外

  • >Channel是雙向的,可以讀也可以寫
//將緩衝區buffer寫入通道
channel.write(sendBuffer);

最後就是,NIO既然是異步非阻塞的,channel自然也具有下面這個特徵:

  • >Channel可以進行異步非阻塞的讀寫
    當然,這一點需要我們手動設置
channel.configureBlocking(false);

有些類型的Channel必須設置成異步非阻塞模式,否則無法進行異步的IO工作,比如SocketChannel和ServerSocketChannel。但也有類型不具備異步,就像FileChannel。

常用的Channel類型有:

  • FileChannel:從文件讀取數據的
  • DatagramChannel:讀寫UDP網絡協議數據
  • SocketChannel:讀寫TCP網絡協議數據
  • ServerSocketChannel:可以監聽TCP連接

Selector選擇器

接着上一章的那個比喻,如果我們把Buffer緩衝區看做碼頭,Channel通道看做航道的話,那麼Selector選擇器可以看做海上的一個調度中心,一個調度中心可以監聽多個航道的事件,當然,這需要航道歸屬在這個調度中心的編制下。

創建一個Selector

Selector selector = Selector.open();

將Channel註冊在某個Selector的管轄範圍之下

Selector是一個對象,它可以被註冊到很多個Channel上,監聽各個Channel上發生的事件,並且能夠根據事件情況決定Channel讀寫。這樣,通過一個線程管理多個Channel,就可以處理大量網絡連接了。

channel.register(selector, SelectionKey.OP_ACCEPT);

上面這行代碼是有返回值的,這個方法會返回一個SelectionKey類型的結構。這個SelectionKey是NIO中事務處理的一個關鍵中間點,通過SelectionKey我們可以獲取很多信息,可以說SelectionKey是整個NIO的業務大腦,關於SelectionKey,我將會放在下一章進行說明,畢竟自己目前的理解還不夠深。

哈哈,階段性…階段性記錄…

參考文章

感謝!

http://blog.csdn.net/suifeng3051/article/details/48160753
Java NIO 詳解(一)

http://walsh.iteye.com/blog/450114
java.nio.Buffer flip()方法jdk中文翻譯錯誤

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