Java IO之NIO

上篇說了最基礎(chǔ)的五種IO模型,相信大家對IO相關(guān)的概念應(yīng)該有了一定的了解,這篇文章主要講講基于多路復(fù)用IO的Java NIO。

背景

Java誕生至今,有好多種IO模型,從最早的Java IO到后來的Java NIO以及最新的Java AIO,每種IO模型都有它自己的特點,詳情請看我的上篇文章Java IO初探,而其中的的Java NIO應(yīng)用非常廣泛,尤其是在高并發(fā)領(lǐng)域,比如我們常見的Netty,Mina等框架,都是基于它實現(xiàn)的,相信大家都有所了解,下面讓我們來看看Java NIO的具體架構(gòu)。

Java NIO架構(gòu)

其實Java NIO模型相對來說也還是比較簡單的,它的核心主要有三個,分別是:Selector、Channel和Buffer,我們先來看看它們之間的關(guān)系:

java-nio

它們之間的關(guān)系很清晰,一個線程對應(yīng)著一個Selector,一個Selector對應(yīng)著多個Channel,一個Channel對應(yīng)著一個Buffer,當(dāng)然這只是通常的做法,一個Channel也可以對應(yīng)多個Selector,一個Channel對應(yīng)著多個Buffer。

Selector

個人認(rèn)為Selector是Java NIO的最大特點,之前我們說過,傳統(tǒng)的Java IO在面對大量IO請求的時候有心無力,因為每個維護每一個IO請求都需要一個線程,這帶來的問題就是,系統(tǒng)資源被極度消耗,吞吐量直線下降,引起系統(tǒng)相關(guān)問題,那么Java NIO是如何解決這個問題的呢?答案就是Selector,簡單來說它對應(yīng)著多路IO復(fù)用中的監(jiān)管角色,它負責(zé)統(tǒng)一管理IO請求,監(jiān)聽相應(yīng)的IO事件,并通知對應(yīng)的線程進行處理,這種模式下就無需為每個IO請求單獨分配一個線程,另外也減少線程大量阻塞,資源利用率下降的情況,所以說Selector是Java NIO的精髓,在Java中我們可以這么寫:

// 打開服務(wù)器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服務(wù)器配置為非阻塞
ssc.configureBlocking(false);
// 進行服務(wù)的綁定
ssc.bind(new InetSocketAddress("localhost", 8001));

// 通過open()方法找到Selector
Selector selector = Selector.open();
// 注冊到selector,等待連接
ssc.register(selector, SelectionKey.OP_ACCEPT);
...

Channel

Channel本意是通道的意思,簡單來說,它在Java NIO中表現(xiàn)的就是一個數(shù)據(jù)通道,但是這個通道有一個特點,那就是它是雙向的,也就是說,我們可以從通道里接收數(shù)據(jù),也可以向通道里寫數(shù)據(jù),不用像Java BIO那樣,讀數(shù)據(jù)和寫數(shù)據(jù)需要不同的數(shù)據(jù)通道,比如最常見的Inputstream和Outputstream,但是它們都是單向的,Channel作為一種全新的設(shè)計,它幫助系統(tǒng)以相對小的代價來保持IO請求數(shù)據(jù)傳輸?shù)奶幚?,但是它并不真正存放?shù)據(jù),它總是結(jié)合著緩存區(qū)(Buffer)一起使用,另外Channel主要有以下四種:

  • FileChannel:讀寫文件時使用的通道
  • DatagramChannel:傳輸UDP連接數(shù)據(jù)時的通道,與Java IO中的DatagramSocket對應(yīng)
  • SocketChannel:傳輸TCP連接數(shù)據(jù)時的通道,與Java IO中的Socket對應(yīng)
  • ServerSocketChannel: 監(jiān)聽套接詞連接時的通道,與Java IO中的ServerSocket對應(yīng)

當(dāng)然其中最重要以及最常用的就是SocketChannel和ServerSocketChannel,也是Java NIO的精髓,ServerSocketChannel可以設(shè)置成非阻塞模式,然后結(jié)合Selector就可以實現(xiàn)多路復(fù)用IO,使用一個線程管理多個Socket連接,具體使用可以參數(shù)上面的代碼。

Buffer

顧名思義,Buffer的含義是緩沖區(qū),它在Java NIO中的主要作用就是作為數(shù)據(jù)的緩沖區(qū)域,Buffer對應(yīng)著某一個Channel,從Channel中讀取數(shù)據(jù)或者向Channel中寫數(shù)據(jù),Buffer與數(shù)組很類似,但是它提供了更多的特性,方便我們對Buffer中的數(shù)據(jù)進行操作,后面我也會主要分析它的三個屬性capacity,position和limit,我們先來看一下Buffer分配時的類別(這里不是指Buffer的具體數(shù)據(jù)類型)即Direct Buffer和Heap Buffer,那么為什么要有這兩種類別的Buffer呢?我們先來看看它們的特性:

Direct Buffer:

  • 直接分配在系統(tǒng)內(nèi)存中;
  • 不需要花費將數(shù)據(jù)庫從內(nèi)存拷貝到Java內(nèi)存中的成本;
  • 雖然Direct Buffer是直接分配中系統(tǒng)內(nèi)存中的,但當(dāng)它被重復(fù)利用時,只有真正需要數(shù)據(jù)的那一頁數(shù)據(jù)會被裝載到真是的內(nèi)存中,其它的還存在在虛擬內(nèi)存中,不會造成實際內(nèi)存的資源浪費;
  • 可以結(jié)合特定的機器碼,一次可以有順序的讀取多字節(jié)單元;
  • 因為直接分配在系統(tǒng)內(nèi)存中,所以它不受Java GC管理,不會自動回收;
  • 創(chuàng)建以及銷毀的成本比較高;

Heap Buffer:

  • 分配在Java Heap,受Java GC管理生命周期,不需要額外維護;
  • 創(chuàng)建成本相對較低;

根據(jù)它們的特性,我們可以大致總結(jié)出它們的適用場景:

如果這個Buffer可以重復(fù)利用,而且你也想多個字節(jié)操作,亦或者你對性能要求很高,可以選擇使用Direct Buffer,但其編碼相對來說會比較復(fù)雜,需要注意的點也更多,反之則用Heap Buffer,Buffer的相應(yīng)創(chuàng)建方法:

//創(chuàng)建Heap Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

//創(chuàng)建Direct Buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

下面我們來看看它的三個屬性:

  • Capacity:顧名思義它的含義是容量,代表著Buffer的最大容量,與數(shù)組的Size很類似,初始化不可更改,除非你改變的Buffer的結(jié)構(gòu);
  • Limit:顧名思義它的含義是界限,代表著Buffer的目前可使用的最大限制,寫模式下,一般Limit等于Capacity,讀模式下需要你自己控制它的值結(jié)合position讀取想要的數(shù)據(jù);
  • Position:顧名思義它的含義是位置,代表著Buffer目前操作的位置,通俗來說,就是你下次對Buffer進行操作的起始位置;

接下來我會用一個圖解的列子幫助大家理解,現(xiàn)在我們假設(shè)有一個容量為10的Buffer,我們先往里面寫入一定字節(jié)的數(shù)據(jù),然后再根據(jù)編碼規(guī)則從其中讀取我們需要的數(shù)據(jù):

1.初始Buffer:

ByteBuffer buffer = ByteBuffer.allocate(10);
init-buffer.png

2.向Buffer中寫入兩個字節(jié):

buffer.put("my".getBytes());
write-buffer-1.png

3.再Buffer中寫入四個字節(jié):

buffer.put("blog".getBytes());
write-buffer-2.png

4.現(xiàn)在我們需要從Buffer中獲取數(shù)據(jù),首先我們先將寫模式轉(zhuǎn)換為讀模式:

  buffer.flip();

我們來看看flip()方法到底做了什么事?

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

從源碼中可以看出,flip方法根據(jù)Buffer目前的相應(yīng)屬性來修改對應(yīng)的屬性,所以flip()方法之后,Buffer目前的狀態(tài):

read-buffer.png

5.接著我們從Buffer中讀取數(shù)據(jù)

從Buffer中讀取數(shù)據(jù)有多種方式,比如get(),get(byte [])等,相關(guān)的具體方法使用可以參考Buffer的官方API文檔,這里我們用最簡單的get()來獲取數(shù)據(jù):

  byte a = buffer.get();
  byte b = buffer.get();

此時Buffer的狀態(tài)如下圖所示:

read-buffer-2.png

我們可以按照這種方式讀取完我們所需數(shù)據(jù),最終調(diào)用clear()方法將Buffer置為初始狀態(tài)。

總結(jié)

這篇文章主要講解了Java NIO中重要的三個組成部分,在實際使用過程也是比較重要的,掌握它們之間的關(guān)系,可以讓你對Java NIO的整個架構(gòu)更加熟悉,理解相對來說也會更加深刻,并分析了這種模式是如何與多路復(fù)用IO模型的映射,了解Java NIO在高并發(fā)場景下優(yōu)勢的原因。

?著作權(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)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,971評論 1 143
  • 簡介 Java NIO 是由 Java 1.4 引進的異步 IO.Java NIO 由以下幾個核心部分組成: Ch...
    永順閱讀 1,859評論 0 15
  • 這兩天了解了一下關(guān)于NIO方面的知識,網(wǎng)上關(guān)于這一塊的介紹只是介紹了一下基本用法,沒有系統(tǒng)的解釋NIO與阻塞、非阻...
    Ruheng閱讀 7,262評論 5 48
  • (轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/) Java NIO: ...
    數(shù)獨題閱讀 873評論 0 3
  • (轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/) Java NIO: ...
    柳岸閱讀 898評論 0 3

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