本文由 GodPan 發表在 ScalaCool 團隊博客。java
上篇說了最基礎的五種IO模型,相信你們對IO相關的概念應該有了必定的瞭解,這篇文章主要講講基於多路複用IO的Java NIO。數據庫
Java誕生至今,有好多種IO模型,從最先的Java IO到後來的Java NIO以及最新的Java AIO,每種IO模型都有它本身的特色,詳情請看個人上篇文章Java IO初探,而其中的的Java NIO應用很是普遍,尤爲是在高併發領域,好比咱們常見的Netty,Mina等框架,都是基於它實現的,相信你們都有所瞭解,下面讓咱們來看看Java NIO的具體架構。數組
其實Java NIO模型相對來講也仍是比較簡單的,它的核心主要有三個,分別是:Selector、Channel和Buffer,咱們先來看看它們之間的關係:緩存
它們之間的關係很清晰,一個線程對應着一個Selecter,一個Selecter對應着多個Channel,一個Channel對應着一個Buffer,固然這只是一般的作法,一個Channel也能夠對應多個Selecter,一個Channel對應着多個Buffer。服務器
我的認爲Selecter是Java NIO的最大特色,以前咱們說過,傳統的Java IO在面對大量IO請求的時候有心無力,由於每一個維護每個IO請求都須要一個線程,這帶來的問題就是,系統資源被極度消耗,吞吐量直線降低,引發系統相關問題,那麼Java NIO是如何解決這個問題的呢?答案就是Selecter,簡單來講它對應着多路IO複用中的監管角色,它負責統一管理IO請求,監聽相應的IO事件,並通知對應的線程進行處理,這種模式下就無需爲每一個IO請求單獨分配一個線程,另外也減小線程大量阻塞,資源利用率降低的狀況,因此說Selecter是Java NIO的精髓,在Java中咱們能夠這麼寫:架構
// 打開服務器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服務器配置爲非阻塞
ssc.configureBlocking(false);
// 進行服務的綁定
ssc.bind(new InetSocketAddress("localhost", 8001));
// 經過open()方法找到Selector
Selector selector = Selector.open();
// 註冊到selector,等待鏈接
ssc.register(selector, SelectionKey.OP_ACCEPT);
...
複製代碼
Channel本意是通道的意思,簡單來講,它在Java NIO中表現的就是一個數據通道,可是這個通道有一個特色,那就是它是雙向的,也就是說,咱們能夠從通道里接收數據,也能夠向通道里寫數據,不用像Java BIO那樣,讀數據和寫數據須要不一樣的數據通道,好比最多見的Inputstream和Outputstream,可是它們都是單向的,Channel做爲一種全新的設計,它幫助系統以相對小的代價來保持IO請求數據傳輸的處理,可是它並不真正存放數據,它老是結合着緩存區(Buffer)一塊兒使用,另外Channel主要有如下四種:併發
固然其中最重要以及最經常使用的就是SocketChannel和ServerSocketChannel,也是Java NIO的精髓,ServerSocketChannel能夠設置成非阻塞模式,而後結合Selecter就能夠實現多路複用IO,使用一個線程管理多個Socket鏈接,具體使用能夠參數上面的代碼。框架
顧名思義,Buffer的含義是緩衝區,它在Java NIO中的主要做用就是做爲數據的緩衝區域,Buffer對應着某一個Channel,從Channel中讀取數據或者向Channel中寫數據,Buffer與數組很相似,可是它提供了更多的特性,方便咱們對Buffer中的數據進行操做,後面我也會主要分析它的三個屬性capacity,position和limit,咱們先來看一下Buffer分配時的類別(這裏不是指Buffer的具體數據類型)即Direct Buffer和Heap Buffer,那麼爲何要有這兩種類別的Buffer呢?咱們先來看看它們的特性:高併發
Direct Buffer:性能
Heap Buffer:
根據它們的特性,咱們能夠大體總結出它們的適用場景:
若是這個Buffer能夠重複利用,並且你也想多個字節操做,亦或者你對性能要求很高,能夠選擇使用Direct Buffer,但其編碼相對來講會比較複雜,須要注意的點也更多,反之則用Heap Buffer,Buffer的相應建立方法:
//建立Heap Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
//建立Direct Buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
複製代碼
下面咱們來看看它的三個屬性:
接下來我會用一個圖解的列子幫助你們理解,如今咱們假設有一個容量爲10的Buffer,咱們先往裏面寫入必定字節的數據,而後再根據編碼規則從其中讀取咱們須要的數據:
1.初始Buffer:
ByteBuffer buffer = ByteBuffer.allocate(10);
複製代碼
2.向Buffer中寫入兩個字節:
buffer.put("my".getBytes());
複製代碼
3.再Buffer中寫入四個字節:
buffer.put("blog".getBytes());
複製代碼
4.如今咱們須要從Buffer中獲取數據,首先咱們先將寫模式轉換爲讀模式:
buffer.flip();
複製代碼
咱們來看看flip()方法到底作了什麼事?
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
複製代碼
從源碼中能夠看出,flip方法根據Buffer目前的相應屬性來修改對應的屬性,因此flip()方法以後,Buffer目前的狀態:
5.接着咱們從Buffer中讀取數據
從Buffer中讀取數據有多種方式,好比get(),get(byte [])等,相關的具體方法使用能夠參考Buffer的官方API文檔,這裏咱們用最簡單的get()來獲取數據:
byte a = buffer.get();
byte b = buffer.get();
複製代碼
此時Buffer的狀態以下圖所示:
咱們能夠按照這種方式讀取完咱們所需數據,最終調用clear()方法將Buffer置爲初始狀態。
這篇文章主要講解了Java NIO中重要的三個組成部分,在實際使用過程也是比較重要的,掌握它們之間的關係,可讓你對Java NIO的整個架構更加熟悉,理解相對來講也會更加深入,並分析了這種模式是如何與多路複用IO模型的映射,瞭解Java NIO在高併發場景下優點的緣由。