在NIO中,數(shù)據(jù)的讀寫操作始終是與緩沖區(qū)相關(guān)聯(lián)的.讀取時信道(SocketChannel)將數(shù)據(jù)讀入緩沖區(qū),寫入時首先要將發(fā)送的數(shù)據(jù)按順序填入緩沖區(qū).緩沖區(qū)是定長的,基本上它只是一個列表,它的所有元素都是基本數(shù)據(jù)類型.ByteBuffer是最常用的緩沖區(qū),它提供了讀寫其他數(shù)據(jù)類型的方法,且信道的讀寫方法只接收ByteBuffer.因此ByteBuffer的用法是有必要牢固掌握的.
1.創(chuàng)建ByteBuffer
1.1 使用allocate()靜態(tài)方法
ByteBuffer
buffer=ByteBuffer.allocate(256);
以上方法將創(chuàng)建一個容量為256字節(jié)的ByteBuffer,如果發(fā)現(xiàn)創(chuàng)建的緩沖區(qū)容量太小,唯一的選擇就是重新創(chuàng)建一個大小合適的緩沖區(qū).
1.2 通過包裝一個已有的數(shù)組來創(chuàng)建
如下,通過包裝的方法創(chuàng)建的緩沖區(qū)保留了被包裝數(shù)組內(nèi)保存的數(shù)據(jù).
ByteBuffer
buffer=ByteBuffer.wrap(byteArray);
如果要將一個字符串存入ByteBuffer,可以如下操作:
String sendString="你好,服務(wù)器. ";
ByteBuffer
sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));
2.回繞緩沖區(qū)
buffer.flip();
這個方法用來將緩沖區(qū)準(zhǔn)備為數(shù)據(jù)傳出狀態(tài),執(zhí)行以上方法后,輸出通道會從數(shù)據(jù)的開頭而不是末尾開始.回繞保持緩沖區(qū)中的數(shù)據(jù)不變,只是準(zhǔn)備寫入而不是讀取.
3.清除緩沖區(qū)
buffer.clear();
這個方法實際上也不會改變緩沖區(qū)的數(shù)據(jù),而只是簡單的重置了緩沖區(qū)的主要索引值.不必為了每次讀寫都創(chuàng)建新的緩沖區(qū),那樣做會降低性能.相反,要重用現(xiàn)在的緩沖區(qū),在再次讀取之前要清除緩沖區(qū).
4.從套接字通道(信道)讀取數(shù)據(jù)
int bytesReaded=socketChannel.read(buffer);
執(zhí)行以上方法后,通道會從socket讀取的數(shù)據(jù)填充此緩沖區(qū),它返回成功讀取并存儲在緩沖區(qū)的字節(jié)數(shù).在默認情況下,這至少會讀取一個字節(jié),或者返回-1指示數(shù)據(jù)結(jié)束.
5.向套接字通道(信道)寫入數(shù)據(jù)
socketChannel.write(buffer);
此方法以一個ByteBuffer為參數(shù),試圖將該緩沖區(qū)中剩余的字節(jié)寫入信道.
ByteBuffer buffer = ByteBuffer.allocate(1024); //分配一定的空間,1024
int i = 90;
buffer.putInt(i);
byte[] array = buffer.array(); //獲取該buffer的數(shù)組,這個數(shù)組是跟該buffer一一對應(yīng)的
for(int j =0; j <4;j++){
System.out.println(Integer.toBinaryString(array[j] & 0xFF));
}
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
等效于
ByteBuffer buffer = ByteBuffer.allocate(1024); //分配一定的空間,1024
反轉(zhuǎn)此緩沖區(qū)。首先對當(dāng)前位置設(shè)置限制,然后將該位置設(shè)置為零。如果已定義了標(biāo)記,則丟棄該標(biāo)記。
當(dāng)將數(shù)據(jù)從一個地方傳輸?shù)搅硪粋€地方時,經(jīng)常將此方法與 compact 方法一起使用。
我最終的理解是: 文檔翻譯得太差了, 把不應(yīng)該翻譯的內(nèi)容也譯成了中文, 所以反而不容易理解.
關(guān)鍵就在以下 2 處:
當(dāng)前位置: 這個可以直觀地理解為緩沖區(qū)中的當(dāng)前數(shù)據(jù)指針, 或是 SQL 中的游標(biāo), 記為 curPointer.
限制: 這個可以理解成實際操作的緩沖區(qū)段的結(jié)束標(biāo)記, 記為 endPointer.
反轉(zhuǎn): 這個完全是對 flip 這個詞不負責(zé)的翻譯, 如果參照 DirectX 里的 flip() 而譯為翻轉(zhuǎn)/翻頁, 那就好理解得多, 就像寫信/看信, 寫/看完一頁后, 翻到下一頁, 眼睛/筆從頁底重新移回頁首.
這個翻轉(zhuǎn)背后的操作其實就是 "把 endPointer 定位到 curPointer 處, 并把 curPointer 設(shè)為 0".
關(guān)于標(biāo)記, 在這里不涉及. 下一句說到常與 compact 方法一起使用, 是可以想像的, 因為 compact 方法對數(shù)據(jù)進
行了壓縮, 有效數(shù)據(jù)的真實長度發(fā)生了變化, 肯定需要用 flip 重新定位結(jié)束標(biāo)記.
在填充, 壓縮等數(shù)據(jù)操作時, curPointer 估計都是自動更新了位置的, 總是指向最后一個有效數(shù)據(jù), 所以每次調(diào)
用 flip() 后, endPointer 就指向了有效數(shù)據(jù)的結(jié)尾, 而 curPointer 指向了 0 (緩沖起始處).
舉個圖例:
(c 和 e 分別代表 curPointer 和 endPointer 兩個指針)
* 先是一個空的 ByteBuffer (大小為 10 字節(jié))
-------------------
-------------------
c
e
* 然后填充 5 字節(jié)數(shù)據(jù)
-------------------
0 1 2 3 4
-------------------
e c
此時, endPointer 尚在 0 處, curPointer 移到了數(shù)據(jù)結(jié)尾.
經(jīng)測試, 此時若取數(shù)據(jù), 將得到 5 個字節(jié), 內(nèi)容通常為 0 (也有可能是未知), 因為實際上取到的是從 c 處到緩沖
區(qū)實際結(jié)束處的 5 個未初始化的字節(jié).
(QZone 字體處理不正確, 此處 c 是在 4 的下面, e 在 0 的下面)
* 調(diào)用一次 flip() 后
-------------------
0 1 2 3 4
-------------------
c e
此時, endPointer 先被移到 curPointer, 然后 curPointer 移到 0.
通過測試可見, ByteBuffer 取數(shù)據(jù)時, 是從 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 則取到緩沖區(qū)結(jié)束.
(QZone 字體處理不正確, 此處 c 是在 0 的下面, e 在 4 的下面)
再看上面代碼的關(guān)鍵片段, 行 8 處調(diào)用 flip() 即有兩個作用, 一是將 curPointer 移到 0, 二是將 endPointer 移到有效數(shù)據(jù)結(jié)尾.
此行可由以下兩行代替:
buff.limit( buff.position());
buff.position( 0 );
可見對其工作原理的理解, 應(yīng)該是正確的
2. 每一次 get 或 put 后, curPointer 都將向緩沖區(qū)尾部移動, 移動量=操作的數(shù)據(jù)量.
3. get/put 均是從 curPointer 起, 到 curPointer + 操作的數(shù)據(jù)長度止.
4. get/put 操作中, 若 curPointer 超過了 endPointer 或緩沖區(qū)總長度, 將拋出 java.nio.BufferUnderflowException 異常.
注: curPointer 和 endPointer 只是為文中方便描述命名的, 實際分別對應(yīng)到 ByteBuffer.position() 和 ByteBuffer.limit() 兩個方法.
比如 我們有初始化一個ByteBuffer 后有
ByteBuffer buffer = ByteBuffer.allocate(1024);
這是 終止位置limit在1024, 而起始位置position在 0
如果我們添加一個數(shù)據(jù),
buffer.putint(90);
這會使起始位置 position 移到4, 也就是說postion始終都在第一個可寫字節(jié)的位置上. limit 則不會發(fā)生改變
而如果這時,我們調(diào)用
buffer.flip();
position轉(zhuǎn)到0, limit轉(zhuǎn)到 4 也就是原來的position 所在位置
這里的flip, 從另外一個角度上來說, 是在讀數(shù)據(jù)時,操作的
然而, 如果我此時在寫
buffer.putInt(90);
就會將原來的覆蓋掉
如果我們在寫, 這時就不行了, 就會重現(xiàn)問題了. 因為我們的limit是4, 我們寫入數(shù)據(jù)不能超過這個limit,(當(dāng)然還有capacity)
所以在寫的時候,最好先清空buffer.clear();
如果真的不想清空, 也可以調(diào)用
buffer.limit(newlimit);
設(shè)置一個較大的limit, 再寫入
當(dāng)然不能超過capacity, 可以等于 capacity