今晚用到 ByteBuffer, 我跟 joy 都是初學 java, 文檔里的中文翻譯實在是看他母親不懂, 暈了半天, 作了幾個測試, 終于把這個類的用法搞清楚了, 順便臆想了哈其工作原理.
// ...
//
// 此段代碼功能為從 t.txt 里復(fù)制所有數(shù)據(jù)到 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 + " 字節(jié), 耗時 " + ( t2 - t1 + 1 ) + " ms." );
...
----------------------------------------------------------------------------------------------------
SDK 文檔里對 ByteBuffer 的說明為:
public abstract class ByteBuffer
extends Buffer
implements Comparable <ByteBuffer>
這說明 ByteBuffer 是繼承于 Buffer 的抽象類, 實現(xiàn)了兩個接口.
行3 通過 allocate() 分配了一塊 1024 字節(jié)的緩沖區(qū), 并返回一個 ByteBuffer 對象. (抽象類不能直接 new)
行6 fcin.read() 將數(shù)據(jù)讀入到 buff. 此處的 read() 是 FileChannel 類的一個虛函數(shù).
行8 buff.flip() 這個調(diào)用就是開頭一直無法理解的部分.
----------------------------------------------------------------------------------------------------
SDK 文檔里的對 flip() 的說明是:
public final Buffer flip()
反轉(zhuǎn)
當將數(shù)據(jù)從一個地方傳輸?shù)搅硪粋€地方時,經(jīng)常將此方法與 compact 方法一起使用。
我最終的理解是: 文檔翻譯得太差了, 把不應(yīng)該翻譯的內(nèi)容也譯成了中文, 所以反而不容易理解.
關(guān)鍵就在以下 2 處:
當前位置
限制
反轉(zhuǎn)
這個翻轉(zhuǎn)背后的操作其實就是 "把 endPointer 定位到 curPointer 處, 并把 curPointer 設(shè)為 0".
關(guān)于標記, 在這里不涉及. 下一句說到常與 compact 方法一起使用, 是可以想像的, 因為 compact 方法對數(shù)據(jù)進
行了壓縮, 有效數(shù)據(jù)的真實長度發(fā)生了變化, 肯定需要用 flip 重新定位結(jié)束標記.
在填充, 壓縮等數(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 處到緩沖
* 調(diào)用一次 flip() 后
-------------------
0 1 2 3 4
-------------------
c e
此時, endPointer 先被移到 curPointer, 然后 curPointer 移到 0.
通過測試可見, ByteBuffer 取數(shù)據(jù)時, 是從 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 則取到緩沖區(qū)結(jié)束.
再看上面代碼的關(guān)鍵片段, 行 8 處調(diào)用 flip() 即有兩個作用, 一是將 curPointer 移到 0, 二是將 endPointer 移到有效數(shù)據(jù)結(jié)尾.
此行可由以下兩行代替:
buff.limit( buff.position());
buff.position( 0 );
可見對其工作原理的理解, 應(yīng)該是正確的.
----------------------------
1. put 數(shù)據(jù)時, 不會自動清除緩沖區(qū)中現(xiàn)有的數(shù)據(jù).
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() 兩個方法.
----------------------------------------------------------------------------------------------------
curPointer 是用 ByteBuffer.position() 取值, 用 ByteBuffer.position( int ) 賦值, 不知道 JDK 為什么要用多態(tài)來實現(xiàn)這兩個功能, 按我的想法, 設(shè)計成 getPosition(), setPosition() 不是要好看好記得多啊.
----------------------------------------------------------------------------------------------------
C++ 里面沒有類似 ByteBuffer 的現(xiàn)成實現(xiàn), 實現(xiàn)上述類似的文件復(fù)制功能, 通常要自己創(chuàng)建管理緩沖區(qū). C++ 里讀寫文件通常用 FileRead(), FileWrite() 函數(shù), 在讀/寫的時候, 可以直接指定讀/寫的數(shù)據(jù)長度, 相比下顯得
直觀方便些, 但 JDK 這個 ByteBuffer 的方式, 確實更方便好用.
ByteBuffer 作為繼承自 Buffer 的抽象類, 實現(xiàn)了對 Byte 型緩沖的管理, 同時 JDK 里還有對應(yīng)其他數(shù)據(jù)類型的
繼承自 Buffer 的抽象類, 分別實現(xiàn)對應(yīng)類型的緩沖管理. 這種設(shè)計減少了編程時的工作. 如果在 C++ 中, 調(diào)用
讀/寫函數(shù)時, 還需要考慮傳入數(shù)據(jù)的類型, 通常用傳入 sizeof(數(shù)據(jù)類型) 的方式指定, 除了函數(shù)調(diào)用時增加耗
費外, 靈活性也更差些.
反過來再想, 為什么 C 要用這種方式? 個人認為, 這是 C 標準庫的實現(xiàn)方式, 因為在不同 OS 平臺上, 對文件和
設(shè)備的訪問方法在系統(tǒng)層不一定相同, 同時硬件平臺(主要是 CPU)上基本數(shù)據(jù)類型寬度也有可能不同, 標準庫通過
sizeof 這個宏在編譯時才能確定數(shù)據(jù)寬度, 所以標準 C 代碼通常可以在不同平臺上重新編譯.
再想 C++ 為什么要用這種方式? 首先, C++ 里沿用 C 標準庫的模式是可以理解的, 其次, 也許只是 C++ 標準庫
里沒有類似的設(shè)計, 說不定早就有第三方通過模板實現(xiàn)的了.
個人認為, ByteBuffer 在實現(xiàn)上, 可以算是一種數(shù)據(jù)結(jié)構(gòu), 在類設(shè)計上, 可以算是一種設(shè)計模式了.