什么是NIO?NIO的原理是什么機制?

來源:https://blog.csdn.net/qq_36520235/

NIO和IO到底有什么區(qū)別?有什么關(guān)系?

首先說一下核心區(qū)別:

NIO是以塊的方式處理數(shù)據(jù),但是IO是以最基礎(chǔ)的字節(jié)流的形式去寫入和讀出的。所以在效率上的話,肯定是NIO效率比IO效率會高出很多。

NIO不在是和IO一樣用OutputStream和InputStream 輸入流的形式來進行處理數(shù)據(jù)的,但是又是基于這種流的形式,而是采用了通道和緩沖區(qū)的形式來進行處理數(shù)據(jù)的。

還有一點就是NIO的通道是可以雙向的,但是IO中的流只能是單向的。

還有就是NIO的緩沖區(qū)(其實也就是一個字節(jié)數(shù)組)還可以進行分片,可以建立只讀緩沖區(qū)、直接緩沖區(qū)和間接緩沖區(qū),只讀緩沖區(qū)很明顯就是字面意思,直接緩沖區(qū)是為加快 I/O 速度,而以一種特殊的方式分配其內(nèi)存的緩沖區(qū)。

補充一點:NIO比傳統(tǒng)的BIO核心區(qū)別就是,NIO采用的是多路復(fù)用的IO模型,普通的IO用的是阻塞的IO模型,兩個之間的效率肯定是多路復(fù)用效率更高

先了解一下什么是通道,什么是緩沖區(qū)的概念

通道是個什么意思?

通道是對原 I/O 包中的流的模擬。到任何目的地(或來自任何地方)的所有數(shù)據(jù)都必須通過一個 Channel 對象(通道)。一個 Buffer 實質(zhì)上是一個容器對象。發(fā)送給一個通道的所有對象都必須首先放到緩沖區(qū)中;同樣地,從通道中讀取的任何數(shù)據(jù)都要讀到緩沖區(qū)中。Channel是一個對象,可以通過它讀取和寫入數(shù)據(jù)。拿 NIO 與原來的 I/O 做個比較,通道就像是流。

正如前面提到的,所有數(shù)據(jù)都通過 Buffer 對象來處理。您永遠不會將字節(jié)直接寫入通道中,相反,您是將數(shù)據(jù)寫入包含一個或者多個字節(jié)的緩沖區(qū)。同樣,您不會直接從通道中讀取字節(jié),而是將數(shù)據(jù)從通道讀入緩沖區(qū),再從緩沖區(qū)獲取這個字節(jié)。

緩沖區(qū)是什么意思:

Buffer 是一個對象, 它包含一些要寫入或者剛讀出的數(shù)據(jù)。在 NIO 中加入 Buffer 對象,體現(xiàn)了新庫與原 I/O 的一個重要區(qū)別。在面向流的 I/O 中,您將數(shù)據(jù)直接寫入或者將數(shù)據(jù)直接讀到 Stream 對象中

在 NIO 庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時,它是直接讀到緩沖區(qū)中的。在寫入數(shù)據(jù)時,它是寫入到緩沖區(qū)中的。任何時候訪問 NIO 中的數(shù)據(jù),您都是將它放到緩沖區(qū)中。

緩沖區(qū)實質(zhì)上是一個數(shù)組。通常它是一個字節(jié)數(shù)組,但是也可以使用其他種類的數(shù)組。但是一個緩沖區(qū)不 僅僅 是一個數(shù)組。緩沖區(qū)提供了對數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀/寫進程

緩沖區(qū)的類型:

ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer

NIO的底層工作原理

先來了解一下buffer的工作機制:
  • capacity 緩沖區(qū)數(shù)組的總長度
  • position 下一個要操作的數(shù)據(jù)元素的位置
  • limit 緩沖區(qū)數(shù)組中不可操作的下一個元素的位置,limit<=capacity
  • mark 用于記錄當(dāng)前 position 的前一個位置或者默認(rèn)是 0

1.這一步其實是當(dāng)我們剛開始初始化這個buffer數(shù)組的時候,開始默認(rèn)是這樣的

2、但是當(dāng)你往buffer數(shù)組中開始寫入的時候幾個字節(jié)的時候就會變成下面的圖,position會移動你數(shù)據(jù)的結(jié)束的下一個位置,這個時候你需要把buffer中的數(shù)據(jù)寫到channel管道中,所以此時我們就需要用這個buffer.flip();方法,

3、當(dāng)你調(diào)用完2中的方法時,這個時候就會變成下面的圖了,這樣的話其實就可以知道你剛剛寫到buffer中的數(shù)據(jù)是在position---->limit之間,然后下一步調(diào)用clear();

4、這時底層操作系統(tǒng)就可以從緩沖區(qū)中正確讀取這 5 個字節(jié)數(shù)據(jù)發(fā)送出去了。在下一次寫數(shù)據(jù)之前我們在調(diào)一下 clear() 方法。緩沖區(qū)的索引狀態(tài)又回到初始位置。(其實這一步有點像IO中的把轉(zhuǎn)運字節(jié)數(shù)組 char[] buf = new char[1024];不足1024字節(jié)的部分給強制刷新出去的意思)

補充:

1、這里還要說明一下 mark,當(dāng)我們調(diào)用 mark()時,它將記錄當(dāng)前 position 的前一個位置,當(dāng)我們調(diào)用 reset 時,position 將恢復(fù) mark 記錄下來的值

2.clear()方法會:清空整個緩沖區(qū)。position將被設(shè)回0,limit被設(shè)置成 capacity的值(這個個人的理解就是當(dāng)你在flip()方法的基礎(chǔ)上已經(jīng)記住你寫入了多少字節(jié)數(shù)據(jù),直接把position到limit之間的也就是你寫入已經(jīng)記住的數(shù)據(jù)給“復(fù)制”到管道中)

3.當(dāng)你把緩沖區(qū)的數(shù)局寫入到管道中的時候,你需要調(diào)用flip()方法將Buffer從寫模式切換到讀模式,調(diào)用flip()方法會將position設(shè)回0,并將limit設(shè)置成之前position的值。buf.flip();(其實我個人理解的就相當(dāng)于先記住緩沖區(qū)緩沖了多少數(shù)據(jù))

NIO 工作代碼示例

public void selector() throws IOException {
//先給緩沖區(qū)申請內(nèi)存空間
        ByteBuffer buffer = ByteBuffer.allocate(1024);
     //打開Selector為了它可以輪詢每個 Channel 的狀態(tài)
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);//設(shè)置為非阻塞方式
        ssc.socket().bind(new InetSocketAddress(8080));
        ssc.register(selector, SelectionKey.OP_ACCEPT);//注冊監(jiān)聽的事件
        while (true) {
            Set selectedKeys = selector.selectedKeys();//取得所有key集合
            Iterator it = selectedKeys.iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                    ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
                 SocketChannel sc = ssChannel.accept();//接受到服務(wù)端的請求
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ);
                    it.remove();
                } else if 
                ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                    SocketChannel sc = (SocketChannel) key.channel();
                    while (true) {
                        buffer.clear();
                        int n = sc.read(buffer);//讀取數(shù)據(jù)
                        if (n <= 0) {
                            break;
                        }
                        buffer.flip();
                    }
                    it.remove();
                }
            }
        }
}

最后給大家看一下整體的NIO的示意圖

NIO和Netty的工作模型對比?

(1)NIO的工作流程步驟:

  1. 首先是先創(chuàng)建ServerSocketChannel 對象,和真正處理業(yè)務(wù)的線程池
  2. 然后給剛剛創(chuàng)建的ServerSocketChannel 對象進行綁定一個對應(yīng)的端口,然后設(shè)置為非阻塞
  3. 然后創(chuàng)建Selector對象并打開,然后把這Selector對象注冊到ServerSocketChannel 中,并設(shè)置好監(jiān)聽的事件,監(jiān)聽 SelectionKey.OP_ACCEPT
  4. 接著就是Selector對象進行死循環(huán)監(jiān)聽每一個Channel通道的事件,循環(huán)執(zhí)行 Selector.select() 方法,輪詢就緒的 Channel
  5. 從Selector中獲取所有的SelectorKey(這個就可以看成是不同的事件),如果SelectorKey是處于 OP_ACCEPT 狀態(tài),說明是新的客戶端接入,調(diào)用 ServerSocketChannel.accept 接收新的客戶端。
  6. 然后對這個把這個接受的新客戶端的Channel通道注冊到ServerSocketChannel上,并且把之前的OP_ACCEPT 狀態(tài)改為SelectionKey.OP_READ讀取事件狀態(tài),并且設(shè)置為非阻塞的,然后把當(dāng)前的這個SelectorKey給移除掉,說明這個事件完成了
  7. 如果第5步的時候過來的事件不是OP_ACCEPT 狀態(tài),那就是OP_READ讀取數(shù)據(jù)的事件狀態(tài),然后調(diào)用本文章的上面的那個讀取數(shù)據(jù)的機制就可以了

(2)Netty的工作流程步驟:

  1. 創(chuàng)建 NIO 線程組 EventLoopGroup 和 ServerBootstrap。
  2. 設(shè)置 ServerBootstrap 的屬性:線程組、SO_BACKLOG 選項,設(shè)置 NioServerSocketChannel 為 Channel,設(shè)置業(yè)務(wù)處理 Handler
  3. 綁定端口,啟動服務(wù)器程序。
  4. 在業(yè)務(wù)處理 TimeServerHandler 中,讀取客戶端發(fā)送的數(shù)據(jù),并給出響應(yīng)

(3)兩者之間的區(qū)別:

  1. OP_ACCEPT 的處理被簡化,因為對于 accept 操作的處理在不同業(yè)務(wù)上都是一致的。
  2. 在 NIO 中需要自己構(gòu)建 ByteBuffer 從 Channel 中讀取數(shù)據(jù),而 Netty 中數(shù)據(jù)是直接讀取完成存放在 ByteBuf 中的。相當(dāng)于省略了用戶進程從內(nèi)核中復(fù)制數(shù)據(jù)的過程。
  3. 在 Netty 中,我們看到有使用一個解碼器 FixedLengthFrameDecoder,可以用于處理定長消息的問題,能夠解決 TCP 粘包讀半包問題,十分方便。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容