Asyncdb(三):Java NIO

本文由 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架構

其實Java NIO模型相對來講也仍是比較簡單的,它的核心主要有三個,分別是:Selector、Channel和Buffer,咱們先來看看它們之間的關係:緩存

java-nio

它們之間的關係很清晰,一個線程對應着一個Selecter,一個Selecter對應着多個Channel,一個Channel對應着一個Buffer,固然這只是一般的作法,一個Channel也能夠對應多個Selecter,一個Channel對應着多個Buffer。服務器

Selecter

我的認爲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

Channel本意是通道的意思,簡單來講,它在Java NIO中表現的就是一個數據通道,可是這個通道有一個特色,那就是它是雙向的,也就是說,咱們能夠從通道里接收數據,也能夠向通道里寫數據,不用像Java BIO那樣,讀數據和寫數據須要不一樣的數據通道,好比最多見的Inputstream和Outputstream,可是它們都是單向的,Channel做爲一種全新的設計,它幫助系統以相對小的代價來保持IO請求數據傳輸的處理,可是它並不真正存放數據,它老是結合着緩存區(Buffer)一塊兒使用,另外Channel主要有如下四種:併發

  • FileChannel:讀寫文件時使用的通道
  • DatagramChannel:傳輸UDP鏈接數據時的通道,與Java IO中的DatagramSocket對應
  • SocketChannel:傳輸TCP鏈接數據時的通道,與Java IO中的Socket對應
  • ServerSocketChannel: 監聽套接詞鏈接時的通道,與Java IO中的ServerSocket對應

固然其中最重要以及最經常使用的就是SocketChannel和ServerSocketChannel,也是Java NIO的精髓,ServerSocketChannel能夠設置成非阻塞模式,而後結合Selecter就能夠實現多路複用IO,使用一個線程管理多個Socket鏈接,具體使用能夠參數上面的代碼。框架

Buffer

顧名思義,Buffer的含義是緩衝區,它在Java NIO中的主要做用就是做爲數據的緩衝區域,Buffer對應着某一個Channel,從Channel中讀取數據或者向Channel中寫數據,Buffer與數組很相似,可是它提供了更多的特性,方便咱們對Buffer中的數據進行操做,後面我也會主要分析它的三個屬性capacity,position和limit,咱們先來看一下Buffer分配時的類別(這裏不是指Buffer的具體數據類型)即Direct Buffer和Heap Buffer,那麼爲何要有這兩種類別的Buffer呢?咱們先來看看它們的特性:高併發

Direct Buffer:性能

  • 直接分配在系統內存中;
  • 不須要花費將數據庫從內存拷貝到Java內存中的成本;
  • 雖然Direct Buffer是直接分配中系統內存中的,但當它被重複利用時,只有真正須要數據的那一頁數據會被裝載到真是的內存中,其它的還存在在虛擬內存中,不會形成實際內存的資源浪費;
  • 能夠結合特定的機器碼,一次能夠有順序的讀取多字節單元;
  • 由於直接分配在系統內存中,因此它不受Java GC管理,不會自動回收;
  • 建立以及銷燬的成本比較高;

Heap Buffer:

  • 分配在Java Heap,受Java GC管理生命週期,不須要額外維護;
  • 建立成本相對較低;

根據它們的特性,咱們能夠大體總結出它們的適用場景:

若是這個Buffer能夠重複利用,並且你也想多個字節操做,亦或者你對性能要求很高,能夠選擇使用Direct Buffer,但其編碼相對來講會比較複雜,須要注意的點也更多,反之則用Heap Buffer,Buffer的相應建立方法:

//建立Heap Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

//建立Direct Buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
複製代碼

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

  • Capacity:顧名思義它的含義是容量,表明着Buffer的最大容量,與數組的Size很相似,初始化不可更改,除非你改變的Buffer的結構;
  • Limit:顧名思義它的含義是界限,表明着Buffer的目前可以使用的最大限制,寫模式下,通常Limit等於Capacity,讀模式下須要你本身控制它的值結合position讀取想要的數據;
  • Position:顧名思義它的含義是位置,表明着Buffer目前操做的位置,通俗來講,就是你下次對Buffer進行操做的起始位置;

接下來我會用一個圖解的列子幫助你們理解,如今咱們假設有一個容量爲10的Buffer,咱們先往裏面寫入必定字節的數據,而後再根據編碼規則從其中讀取咱們須要的數據:

1.初始Buffer:

ByteBuffer buffer = ByteBuffer.allocate(10);
複製代碼

init-buffer

2.向Buffer中寫入兩個字節:

buffer.put("my".getBytes());
複製代碼

write-buffer-1

3.再Buffer中寫入四個字節:

buffer.put("blog".getBytes());
複製代碼

write-buffer-2

4.如今咱們須要從Buffer中獲取數據,首先咱們先將寫模式轉換爲讀模式:

buffer.flip();
複製代碼

咱們來看看flip()方法到底作了什麼事?

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

從源碼中能夠看出,flip方法根據Buffer目前的相應屬性來修改對應的屬性,因此flip()方法以後,Buffer目前的狀態:

read-buffer

5.接着咱們從Buffer中讀取數據

從Buffer中讀取數據有多種方式,好比get(),get(byte [])等,相關的具體方法使用能夠參考Buffer的官方API文檔,這裏咱們用最簡單的get()來獲取數據:

byte a = buffer.get();
  byte b = buffer.get();
複製代碼

此時Buffer的狀態以下圖所示:

read-buffer-2

咱們能夠按照這種方式讀取完咱們所需數據,最終調用clear()方法將Buffer置爲初始狀態。

總結

這篇文章主要講解了Java NIO中重要的三個組成部分,在實際使用過程也是比較重要的,掌握它們之間的關係,可讓你對Java NIO的整個架構更加熟悉,理解相對來講也會更加深入,並分析了這種模式是如何與多路複用IO模型的映射,瞭解Java NIO在高併發場景下優點的緣由。

相關文章
相關標籤/搜索