java NIO幾個重要的概念

java NIO幾個重要的概念

在當下比較流行的分佈式系統(中間件或者計算框架)中,底層高併發的基礎實現都用到netty,netty和mina很類似,但是netty比mina穩定,雖然效率沒有mina高,相對來說Netty的社區發展比較活躍,而且有豐富的文檔供大家學習,所以netty發展比mina快很多。java NIO是Netty高併發的基礎。在前面java IO小節中粗略的講了一下什麼是NIO,本篇將重點講解幾個NIO中幾個重要的概念,緩衝區Buffer,通道channel和多路複用器selector。

緩衝區Buffer

在NIO庫中,的Buffer用於和NIO通道進行交互,緩衝區本質上是一塊可以寫入數據,然後可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問該塊內存。java.nio.Buffer下有七個子類,這些子類緩衝區分別用於存儲不同類型的數據。分別是:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer ,都是針對基本數據類型,沒有針對布爾型的。在實際中常用到的buffer函數如下幾個:
所有緩衝區都有4個屬性:capacity、limit、position、mark,並遵循:capacity>=limit>=position>=mark>=0,下表格是對着4個屬性的解釋:

屬性 描述
Capacity 容量,即可以容納的最大數據量;在緩衝區創建時被設定並且不能改變
Limit 上界,緩衝區中當前數據量
Position 位置,下一個要被讀或寫的元素的索引
Mark 標記,調用mark()來設置mark=position,再調用reset()可以讓position恢復到標記的位置即position=mark

使用Buffer讀寫數據一般遵循以下四個步驟:

  1. 寫入數據到Buffer
  2. 調用flip()方法
  3. 從Buffer中讀取數據
  4. 調用clear()方法或者compact()方法

當向buffer寫入數據時,buffer會記錄下寫了多少數據。一旦要讀取數據,需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有數據。

一旦讀完了所有的數據,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:調用clear()或compact()方法。clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。

 SocketChannel sc = (SocketChannel)key.channel();
 ByteBuffer readbuffer = ByteBuffer.allocate(1024);
                try {
                    int readBytes = sc.read(readbuffer);
                    if(readBytes>0){
                        readbuffer.flip();
                        byte[] bytes = new byte[readbuffer.remaining()];
                        readbuffer.get(bytes);
                        String body = new String(bytes,"UTF-8");
                        System.out.println("the time server receive order :"+body);
                        String current = "QUERY TIME ORDER".equalsIgnoreCase(body)?new java.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";
                        doWrite(sc,current);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

以上是服務器端從buffer中獲取數據的代碼段,首先申請一個大小爲1M的ByteBuffer,flip()重置當前的position爲0;


Channel通道

Channel是一個通道,網絡數據通過Channel讀取和寫入。通道是全雙工的,所以比流更好的映射底層操作系統API。
Java NIO的通道類似流,但又有些不同:
既可以從通道中讀取數據,又可以寫數據到通道。但流的讀寫通常是單向的。
通道可以異步地讀寫。
通道中的數據總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入。

從通道中讀取數據和寫入數據流程如下圖

這裏寫圖片描述

正如上面所說,從通道讀取數據到緩衝區,從緩衝區寫入數據到通道。如下圖所示:
這些是Java NIO中最重要的通道的實現:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
FileChannel 從文件中讀寫數據。
DatagramChannel 能通過UDP讀寫網絡中的數據。
SocketChannel 能通過TCP讀寫網絡中的數據。
ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。
基本的Channel用例如下:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {

System.out.println("Read " + bytesRead);
buf.flip();

while(buf.hasRemaining()){
System.out.print((char) buf.get());
}

buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();

buf.flip() 的調用,首先讀取數據到Buffer,然後反轉Buffer,接着再從Buffer中讀取數據。

selector多路複用器

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否爲諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接。
僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道。事實上,可以只用一個線程處理所有的通道。對於操作系統來說,線程之間上下文切換的開銷很大,而且每個線程都要佔用系統的一些資源(如內存)。因此,使用的線程越少越好。

在使用selector中通常包括一下幾個步驟:
1、Selector的創建
通過調用Selector.open()方法創建一個Selector

Selector selector = Selector.open();

2、向Selector註冊後通道
將Channel和Selector配合使用,必須將channel註冊到selector上。通過SelectableChannel.register()方法來實現

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
    Selectionkey.OP_READ);

與Selector一起使用時,Channel必須處於非阻塞模式下。這意味着不能將FileChannel與Selector一起使用,因爲FileChannel不能切換到非阻塞模式。而套接字通道都可以。
四種不同類型的事件:
Connect
Accept
Read
Write
通道觸發了一個事件意思是該事件已經就緒。所以,某個channel成功連接到另一個服務器稱爲“連接就緒”。一個server socket channel準備好接收新進入的連接稱爲“接收就緒”。一個有數據可讀的通道可以說是“讀就緒”。等待寫數據的通道可以說是“寫就緒”。
3、實例代碼

  while(!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> selectKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()){
                    key = it.next();
                    it.remove();
                    handleInput(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

在程序中都是通過循環不斷的輪詢哈找註冊在其上的Channel,如果某個Channel發生了讀或者寫的事件,那麼這個Channel就會處於就緒狀態,會被selector輪詢出來,然後通過SelectionKey可以獲取就緒的Channel集合,進行後續的IO操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章