我們知道,netty直接與jdk的原生nio開發的,可以說是jdk nio的增強,所以理解jdk nio的機制就變得非常重要,接下來將介紹jdk中關於nio的幾個非常重要的組件, 示例源碼: https://github.com/jsbintask22/netty-learning
ByteBuffer介紹
對比jdk bio的寫法,在從Socket讀取數據的時候,不管是客戶端還是服務端,我們通常需要將字節讀取到一個字節數組,知道讀取的數據長度滿足所需要的條件:
InputStream is = socket.getInputStream();
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) != -1) {
System.out.println("client msg: " + new String(buff, 0, len));
}
這裏的buff我們就可以認爲是一個數據容器,後續的其他處理(比如這裏的 new String)都會從這個數據容器中取。
而在nio編程中,這個字節數組改爲了一個可重複使用的容器Buffer(java.nio.Buffer),它有多種同類型的基本類型子類,它的結構如下:
假設有一個長度爲n的容器。
- position指針代表洗一個可以讀或者可以寫入的位置索引,初始狀態則指向0
- limit代表下一個不能寫入或者讀取的位置,初始位置則指向n(注意索引是從0開始的)
- capacity總是表示容器的大小,總是爲n
- mark作爲一個標誌爲,當調用mark()方法時,會將position賦值給mark, 這樣在下次調用reset()方法的時候可以讓position回到上次標誌的位置
有了以上這些,在寫入或者讀取時總是會移動position的位置,而如果想重置復原則需要移動. 它們直接總是會滿足這樣的關係: mark <= position <= limit <= capacity
這裏指的注意的是 position 代表的是下一個可以讀或者可以寫的位置,所以讀寫均會移動位置。
Buffer使用
以IntBuffer爲例,介紹下它的使用,IntBuffer繼承自Buffer,內部使用一個整型數組存儲數據 final int[] hb;
- 向容器中存儲數據:
put(int i);
,put(int index, int i);
從指定位置的存儲不會移動 position 指針的位置。 - 獲取數據:
int get();
,int get(int index);
從指定索引位置獲取數據不會移動 positon 位置. - 檢查是否有有效數據: 檢查position 是否小於limit即可 .
return position < limit;
IntBuffer intBuffer = IntBuffer.allocate(10); // 1
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put((i + 1)); // 2
}
intBuffer.flip(); // 3
while (intBuffer.hasRemaining()) { // 4
int i = intBuffer.get(); // 5
System.out.println(i);
}
intBuffer.rewind();
while (intBuffer.hasRemaining()) {
int i = intBuffer.get();
System.out.println(i);
}
- 分配一個字節數據,默認在heap上(內部一個長度爲10的整型數據)
- 寫入數據,會移動 position 的位置
- 翻轉數據區域,將已經寫入了數據的區域用limit和position標識出來,即:
limit = position; position = 0; remark = -1;
- 檢查是否有有效數據.
- 獲取當前position指針的數據,並且向前移動
最後介紹下 rewind, flip, clear三個方法的區別
- clear爲將所有指針重置爲初始狀態:
position = 0; limit = capacity; mark = -1;
,一般用於在進行一系列讀寫操作後需要重用該容器進行重置. - flip爲將有效數據隔離,比如put數據後,調用該方法將數據讀取出來
limit = position; position = 0; remark = -1;
- rewind用於讀取了數據後需要重複讀取數據,即將position指針重置爲0即可,
position = 0; mark = -1;
總結
- 在bio編程中,我們通常使用字節數據向socket寫入數據或者讀取數據到字節數據,而在nio中則一般使用Buffer進行該操作,它有多個基本類型子類,最常用的即是ByteBuffer
- Buffer的讀寫原理爲移動其中的position,limit指針
- rewind,flip, clear三者區別
擴展: 對於Buffer而言,直接使用子類的allocate方法分配數組在heap上,它還可以分配在堆外內存,稱爲直接內存,可以實現零拷貝,提高效率