NIO和BIO

講講NIO

傳統的IO流是阻塞式的,會一直監聽一個ServerSocket,在調用 read等方法時,他一直等到數據到來或者緩衝區已滿時才返回,調用accept也是一直阻塞到有客戶端連接纔會返回,每個客戶端接過來後,服務端都會啓動一個線程去處理該客戶端的請求。並且多線程處理多個連接,每個線程擁有自己的棧空間並且佔用一些CPU時間,每個線程遇到外部未準備好的時候,都會阻塞掉。阻塞的結果就是會芾來大量的進程上下文切換。

對於NIO,它是非阻塞式,核心類:

  1. Buffer爲所有的原始類型提供(Buffer)緩存支持。
  2. Charset字符集編碼解碼解決方案
  3. Channel—個新的原始I/O抽象,用於讀寫Buffer類型,通道可以認爲是一種連接,可以是到特定設備,程序或者是網絡的連接。

NIO和BIO/IO的主要區別

在這裏插入圖片描述
1、面向流與面向緩衝
Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。JavaIO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據,如果需要前後移動從中間中讀取的數據,需要先將它緩存到一個緩衝區。javaNIO的緩衝導向方法略有不同。數據會讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢置是否該緩衝區中包含所有您需要處理的數據。且需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏尚未處理的數據。
2、阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
3、選擇器(Selectors)
Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

NIO進階

在 NIO 庫中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的。在寫入數據時,它是寫入到緩衝區中的。任何時候訪問 NIO 中的數據,您都是將它放到緩衝區中。
緩衝區實質上是一個數組。通常它是一個字節數組,但是也可以使用其他種類的數組。但是一個緩衝區不僅僅 是一個數組。緩衝區提供了對數據的結構化訪問,而且還可以跟蹤系統的讀/寫進程。**最常用的緩衝區類型是 ByteBuffer。**一個 ByteBuffer 可以在其底層字節數組上進行 get/set 操作(即字節的獲取和設置)。
NIO的一個時刻也只能進行一個channel的操作,將本次所有selectedKeys的挨個處理

Select的實現

1、創建ServerSocketChannel實例serverSocketChannel,並bind到指定端口。
2、創建Selector實例selector;
3、將serverSocketChannel註冊到selector,並指定事件OP_ACCEPT。
4、while循環執行
4.1、調用select方法,該方法會阻塞等待,直到有一個或多個通道準備好了I/O操作或等待超時。
4.2、獲取選取的鍵列表;
4.3、循環鍵集中的每個鍵:
4.3.a、獲取通道,並從鍵中獲取附件(如果添加了附件);
4.3.b、確定準備就緒的操縱並執行,如果是accept操作,將接收的信道設置爲非阻塞模式,並註冊到選擇器;
4.3.c、如果需要,修改鍵的興趣操作集;
4.3.d、從已選鍵集中移除鍵

重要的步驟

打開一個 ServerSocketChannel

爲了接收連接,我們需要一個 ServerSocketChannel。事實上,我們要監聽的每一個端口都需要有一個ServerSocketChannel 。對於每一個端口,我們打開一個 ServerSocketChannel,如下所示:

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking( false );
 
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress( ports[i] );
ss.bind( address );

第一行創建一個新的 ServerSocketChannel ,最後三行將它綁定到給定的端口。第二行將 ServerSocketChannel 設置爲 非阻塞的 。我們必須對每一個要使用的套接字通道調用這個方法,否則異步 I/O 就不能工作。

循環集中的每個鍵

下一步是將新打開的 ServerSocketChannels 註冊到 Selector上。爲此我們使用 ServerSocketChannel.register() 方法,如下所示:
1 SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );
register() 的第一個參數總是這個 Selector。第二個參數是 OP_ACCEPT,這裏它指定我們想要監聽 accept 事件,也就是在新的連接建立時所發生的事件。這是適用於 ServerSocketChannel 的唯一事件類型。
請注意對 register() 的調用的返回值。 SelectionKey 代表這個通道在此 Selector 上的這個註冊。當某個 Selector 通知您某個傳入事件時,它是通過提供對應於該事件的 SelectionKey 來進行的。SelectionKey 還可以用於取消通道的註冊。
現在已經註冊了我們對一些 I/O 事件的興趣,下面將進入主循環。使用 Selectors 的幾乎每個程序都像下面這樣使用內部循環:

int num = selector.select();
 
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
 
while (it.hasNext()) {
     SelectionKey key = (SelectionKey)it.next();
     // ... deal with I/O event ...
}

首先,我們調用 Selector 的 select() 方法。這個方法會阻塞,直到至少有一個已註冊的事件發生。當一個或者更多的事件發生時, select() 方法將返回所發生的事件的數量。
接下來,我們調用 SelectorselectedKeys() 方法,它返回發生了事件的 SelectionKey 對象的一個 集合 。
我們通過迭代 SelectionKeys 並依次處理每個 SelectionKey 來處理事件。對於每一個 SelectionKey,您必須確定發生的是什麼 I/O 事件,以及這個事件影響哪些 I/O 對象。

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