1. 程式人生 > 實用技巧 >java NIO 總結

java NIO 總結

Java NIO 由以下幾個核心部分組成:

  • Channels
  • Buffers
  • Selectors

Channel 和 Buffer

基本上,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 資料可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。這裡有個圖示:

Channel和Buffer有好幾種類型。下面是JAVA NIO中的一些主要Channel的實現:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

Selector

Selector允許單執行緒處理多個 Channel。如果你的應用打開了多個連線(通道),但每個連線的流量都很低,使用Selector就會很方便。例如,在一個聊天伺服器中。

這是在一個單執行緒中使用一個Selector處理3個Channel的圖示:

要使用Selector,得向Selector註冊Channel,然後呼叫它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,執行緒就可以處理這些事件,事件的例子有如新連線進來,資料接收等。

Java NIO的通道類似流,但又有些不同:

  • 既可以從通道中讀取資料,又可以寫資料到通道。但流的讀寫通常是單向的。
  • 通道可以非同步地讀寫。
  • 通道中的資料總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入。

FileChannel 從檔案中讀寫資料。

DatagramChannel 能通過UDP讀寫網路中的資料。

SocketChannel 能通過TCP讀寫網路中的資料。

ServerSocketChannel可以監聽新進來的TCP連線,像Web伺服器那樣。對每一個新進來的連線都會建立一個SocketChannel。


Buffer

Java NIO中的Buffer用於和NIO通道進行互動。如你所知,資料是從通道讀入緩衝區,從緩衝區寫入到通道中的。

緩衝區本質上是一塊可以寫入資料,然後可以從中讀取資料的記憶體。這塊記憶體被包裝成NIO Buffer物件,並提供了一組方法,用來方便的訪問該塊記憶體。

Buffer的基本用法

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

  1. 寫入資料到Buffer
  2. 呼叫flip()
    方法
  3. 從Buffer中讀取資料
  4. 呼叫clear()方法或者compact()方法

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

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


intbytesRead = inChannel.read(buf);//read into buffer.

bytesRead 這個值為 channel寫入buffer,偏移值,

  • 當為 -1,說明說明客戶端的資料傳送完畢,並且主動的close socket
  • 當為 0 ,是某一時刻socketChannel中當前(注意是當前)沒有資料可以讀,這時會返回0,最後一種情況就是客戶端的資料傳送完畢了(注意看後面的程式裡有這樣子的程式碼),這個時候客戶端想獲取服務端的反饋呼叫了recv函式,若服務端繼續read,這個時候就會返回0。
  • 當為 其他, 說明buffer的當前limit

Buffer的分配

要想獲得一個Buffer物件首先要進行分配。 每一個Buffer類都有一個allocate方法。下面是一個分配48位元組capacity的ByteBuffer的例子。

ByteBuffer buf = ByteBuffer.allocate(48);

這是分配一個可儲存1024個字元的CharBuffer:

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中寫資料

寫資料到Buffer有兩種方式:

  • 從Channel寫到Buffer。
  • 通過Buffer的put()方法寫到Buffer裡。

從Channel寫到Buffer的例子

int bytesRead = inChannel.read(buf); //read into buffer.

通過buffer.put() 方法

    buf.put(127);

從Buffer中讀取資料

從Buffer中讀取資料有兩種方式:

  1. 從Buffer讀取資料到Channel。
  2. 使用get()方法從Buffer中讀取資料。

從Buffer讀取資料到Channel的例子:

//read from buffer into channel.

int bytesWritten = inChannel.write(buf);

使用get()方法從Buffer中讀取資料的例子

byte aByte = buf.get();

channel 的read 和 write 是針對channel 本身而言的, read() 就是往channel裡寫資料,write() 就是channel 往buffer 中寫資料.

get方法有很多版本,允許你以不同的方式從Buffer中讀取資料。例如,從指定position讀取,或者從Buffer中讀取資料到位元組陣列。更多Buffer實現的細節參考JavaDoc。

flip()方法

flip方法將Buffer從寫模式切換到讀模式。呼叫flip()方法會將position設回0,並將limit設定成之前position的值。

換句話說,position現在用於標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 現在能讀取多少個byte、char等。


selector

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好準備的元件。這樣,一個單獨的執行緒可以管理多個channel,從而管理多個網路連線。

Selector的建立

通過呼叫Selector.open()方法建立一個Selector,如下:

Selector selector = Selector.open();

向Selector註冊通道

為了將Channel和Selector配合使用,必須將channel註冊到selector上。通過SelectableChannel.register()方法來實現,如下:

channel.configureBlocking(false); // 將channel 設定為非阻塞
SelectionKey key = channel.register(selector,Selectionkey.OP_READ); // 將channel 註冊到selector 上,事件為OP_READ

與Selector一起使用時,Channel必須處於非阻塞模式下。

Channel + Selector

從SelectionKey訪問Channel和Selector很簡單。如下:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

selectedKeys()

一旦呼叫了select()方法,並且返回值表明有一個或更多個通道就緒了,然後可以通過呼叫selector的selectedKeys()方法,訪問“已選擇鍵集(selected key set)”中的就緒通道。如下所示:

Set selectedKeys = selector.selectedKeys();

SocketChannel

Java NIO中的SocketChannel是一個連線到TCP網路套接字的通道。可以通過以下2種方式建立SocketChannel:

  1. 開啟一個SocketChannel並連線到網際網路上的某臺伺服器。
  2. 一個新連線到達ServerSocketChannel時,會建立一個SocketChannel。

開啟 SocketChannel

下面是SocketChannel的開啟方式:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

從 SocketChannel 讀取資料

要從SocketChannel中讀取資料,呼叫一個read()的方法之一。以下是例子:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

首先,分配一個Buffer。從SocketChannel讀取到的資料將會放到這個Buffer中。

然後,呼叫SocketChannel.read()。該方法將資料從SocketChannel 讀到Buffer中。read()方法返回的int值表示讀了多少位元組進Buffer裡。如果返回的是-1,表示已經讀到了流的末尾(連線關閉了)。

ServerSocketChannel

Java NIO中的 ServerSocketChannel 是一個可以監聽新進來的TCP連線的通道, 就像標準IO中的ServerSocket一樣。ServerSocketChannel類在 java.nio.channels包中。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    //do something with socketChannel...
}

監聽新進來的連線

通過 ServerSocketChannel.accept() 方法監聽新進來的連線。當 accept()方法返回的時候,它返回一個包含新進來的連線的 SocketChannel。因此, accept()方法會一直阻塞到有新連線到達。

通常不會僅僅只監聽一個連線,在while迴圈中呼叫 accept()方法. 如下面的例子:

while(true){
    SocketChannel socketChannel =
      serverSocketChannel.accept();

    //do something with socketChannel...
}