今晚用到 ByteBuffer, 我跟 joy 都是初學 java, 文檔里的中文翻譯實在是看他母親不懂, 暈了半天, 作了幾個測試, 終于把這個類的用法搞清楚了, 順便臆想了哈其工作原理.
// ...
//
// 此段代碼功能為從 t.txt 里復制所有數據到 out_j.txt:
//
...
1 FileChannel fcin = new FileInputStream( "d:/t.txt" ).getChannel();
2 FileChannel fcout = new FileOutputStream( new File( "d:/out_j.txt" )).getChannel();
3 ByteBuffer buff = ByteBuffer.allocate( 1024 );
4 long t1 = System.currentTimeMillis();
5
6 while( fcin.read( buff ) != -1 )
7 {
8 buff.flip();
9 fcout.write( buff );
10 buff.clear();
11 }
12
13 long t2 = System.currentTimeMillis();
14 long size = fcin.size();
15 javax.swing.JOptionPane.showMessageDialog( null, size + " 字節, 耗時 " + ( t2 - t1 + 1 ) + " ms." );
...
----------------------------------------------------------------------------------------------------
SDK 文檔里對 ByteBuffer 的說明為:
public abstract class ByteBuffer
extends Buffer
implements Comparable <ByteBuffer>
這說明 ByteBuffer 是繼承于 Buffer 的抽象類, 實現了兩個接口.
行3 通過 allocate() 分配了一塊 1024 字節的緩沖區, 并返回一個 ByteBuffer 對象. (抽象類不能直接 new)
行6 fcin.read() 將數據讀入到 buff. 此處的 read() 是 FileChannel 類的一個虛函數.
行8 buff.flip() 這個調用就是開頭一直無法理解的部分.
----------------------------------------------------------------------------------------------------
SDK 文檔里的對 flip() 的說明是:
public final Buffer flip()
反轉
當將數據從一個地方傳輸到另一個地方時,經常將此方法與 compact 方法一起使用。
我最終的理解是: 文檔翻譯得太差了, 把不應該翻譯的內容也譯成了中文, 所以反而不容易理解.
關鍵就在以下 2 處:
當前位置
限制
反轉
這個翻轉背后的操作其實就是 "把 endPointer 定位到 curPointer 處, 并把 curPointer 設為 0".
關于標記, 在這里不涉及. 下一句說到常與 compact 方法一起使用, 是可以想像的, 因為 compact 方法對數據進
行了壓縮, 有效數據的真實長度發生了變化, 肯定需要用 flip 重新定位結束標記.
在填充, 壓縮等數據操作時, curPointer 估計都是自動更新了位置的, 總是指向最后一個有效數據, 所以每次調
用 flip() 后, endPointer 就指向了有效數據的結尾, 而 curPointer 指向了 0 (緩沖起始處).
(c 和 e 分別代表 curPointer 和 endPointer 兩個指針)
* 先是一個空的 ByteBuffer (大小為 10 字節)
-------------------
-------------------
c
e
* 然后填充 5 字節數據
-------------------
0 1 2 3 4
-------------------
e c
此時, endPointer 尚在 0 處, curPointer 移到了數據結尾.
經測試, 此時若取數據, 將得到 5 個字節, 內容通常為 0 (也有可能是未知), 因為實際上取到的是從 c 處到緩沖
* 調用一次 flip() 后
-------------------
0 1 2 3 4
-------------------
c e
此時, endPointer 先被移到 curPointer, 然后 curPointer 移到 0.
通過測試可見, ByteBuffer 取數據時, 是從 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 則取到緩沖區結束.
再看上面代碼的關鍵片段, 行 8 處調用 flip() 即有兩個作用, 一是將 curPointer 移到 0, 二是將 endPointer 移到有效數據結尾.
此行可由以下兩行代替:
buff.limit( buff.position());
buff.position( 0 );
可見對其工作原理的理解, 應該是正確的.
----------------------------
1. put 數據時, 不會自動清除緩沖區中現有的數據.
2. 每一次 get 或 put 后, curPointer 都將向緩沖區尾部移動, 移動量=操作的數據量.
3. get/put 均是從 curPointer 起, 到 curPointer + 操作的數據長度止.
4. get/put 操作中, 若 curPointer 超過了 endPointer 或緩沖區總長度, 將拋出 java.nio.BufferUnderflowException 異常.
注: curPointer 和 endPointer 只是為文中方便描述命名的, 實際分別對應到 ByteBuffer.position() 和 ByteBuffer.limit() 兩個方法.
----------------------------------------------------------------------------------------------------
curPointer 是用 ByteBuffer.position() 取值, 用 ByteBuffer.position( int ) 賦值, 不知道 JDK 為什么要用多態來實現這兩個功能, 按我的想法, 設計成 getPosition(), setPosition() 不是要好看好記得多啊.
----------------------------------------------------------------------------------------------------
C++ 里面沒有類似 ByteBuffer 的現成實現, 實現上述類似的文件復制功能, 通常要自己創建管理緩沖區. C++ 里讀寫文件通常用 FileRead(), FileWrite() 函數, 在讀/寫的時候, 可以直接指定讀/寫的數據長度, 相比下顯得
直觀方便些, 但 JDK 這個 ByteBuffer 的方式, 確實更方便好用.
ByteBuffer 作為繼承自 Buffer 的抽象類, 實現了對 Byte 型緩沖的管理, 同時 JDK 里還有對應其他數據類型的
繼承自 Buffer 的抽象類, 分別實現對應類型的緩沖管理. 這種設計減少了編程時的工作. 如果在 C++ 中, 調用
讀/寫函數時, 還需要考慮傳入數據的類型, 通常用傳入 sizeof(數據類型) 的方式指定, 除了函數調用時增加耗
費外, 靈活性也更差些.
反過來再想, 為什么 C 要用這種方式? 個人認為, 這是 C 標準庫的實現方式, 因為在不同 OS 平臺上, 對文件和
設備的訪問方法在系統層不一定相同, 同時硬件平臺(主要是 CPU)上基本數據類型寬度也有可能不同, 標準庫通過
sizeof 這個宏在編譯時才能確定數據寬度, 所以標準 C 代碼通常可以在不同平臺上重新編譯.
再想 C++ 為什么要用這種方式? 首先, C++ 里沿用 C 標準庫的模式是可以理解的, 其次, 也許只是 C++ 標準庫
里沒有類似的設計, 說不定早就有第三方通過模板實現的了.
個人認為, ByteBuffer 在實現上, 可以算是一種數據結構, 在類設計上, 可以算是一種設計模式了.