|
Posted on 2011-05-18 10:53 Astro.Qi 閱讀(417) 評論(4) 編輯 收藏 所屬分類: Java
今晚用到 ByteBuffer, 我跟 joy 都是初學 java, 文檔里的中文翻譯實在是看他母親不懂, 暈了半天, 作了幾個測試, 終于把這個類的用法搞清楚了, 順便臆想了哈其工作原理.
先列點代碼片段: // ... // // 此段代碼功能為從 t.txt 里復制所有數(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() 反轉此緩沖區(qū)。首先對當前位置設置限制,然后將該位置設置為零。如果已定義了標記,則丟棄該標記。 當將數(shù)據(jù)從一個地方傳輸?shù)搅硪粋€地方時,經(jīng)常將此方法與 compact 方法一起使用。
我最終的理解是: 文檔翻譯得太差了, 把不應該翻譯的內(nèi)容也譯成了中文, 所以反而不容易理解. 關鍵就在以下 2 處:
當前位置: 這個可以直觀地理解為緩沖區(qū)中的當前數(shù)據(jù)指針, 或是 SQL 中的游標, 記為 curPointer. 限制: 這個可以理解成實際操作的緩沖區(qū)段的結束標記, 記為 endPointer. 反轉: 這個完全是對 flip 這個詞不負責的翻譯, 如果參照 DirectX 里的 flip() 而譯為翻轉/翻頁, 那就好理解得多, 就像寫信/看信, 寫/看完一頁后, 翻到下一頁, 眼睛/筆從頁底重新移回頁首. 這個翻轉背后的操作其實就是 "把 endPointer 定位到 curPointer 處, 并把 curPointer 設為 0".
關于標記, 在這里不涉及. 下一句說到常與 compact 方法一起使用, 是可以想像的, 因為 compact 方法對數(shù)據(jù)進 行了壓縮, 有效數(shù)據(jù)的真實長度發(fā)生了變化, 肯定需要用 flip 重新定位結束標記.
在填充, 壓縮等數(shù)據(jù)操作時, curPointer 估計都是自動更新了位置的, 總是指向最后一個有效數(shù)據(jù), 所以每次調(diào) 用 flip() 后, endPointer 就指向了有效數(shù)據(jù)的結尾, 而 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ù)結尾. 經(jīng)測試, 此時若取數(shù)據(jù), 將得到 5 個字節(jié), 內(nèi)容通常為 0 (也有可能是未知), 因為實際上取到的是從 c 處到緩沖區(qū)實際結束處的 5 個未初始化的字節(jié). * 調(diào)用一次 flip() 后-------------------0 1 2 3 4-------------------c e此時, endPointer 先被移到 curPointer, 然后 curPointer 移到 0.通過測試可見, ByteBuffer 取數(shù)據(jù)時, 是從 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 則取到緩沖區(qū)結束. 再看上面代碼的關鍵片段, 行 8 處調(diào)用 flip() 即有兩個作用, 一是將 curPointer 移到 0, 二是將 endPointer 移到有效數(shù)據(jù)結尾.此行可由以下兩行代替:buff.limit( buff.position());buff.position( 0 );可見對其工作原理的理解, 應該是正確的.----------------------------------------------------------------------------------------------------
總結如下: 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 只是為文中方便描述命名的, 實際分別對應到 ByteBuffer.position() 和 ByteBuffer.limit() 兩個方法.
----------------------------------------------------------------------------------------------------
疑惑: curPointer 是用 ByteBuffer.position() 取值, 用 ByteBuffer.position( int ) 賦值, 不知道 JDK 為什么要用多態(tài)來實現(xiàn)這兩個功能, 按我的想法, 設計成 getPosition(), setPosition() 不是要好看好記得多啊.
----------------------------------------------------------------------------------------------------
跟 C++ 的簡單比較: C++ 里面沒有類似 ByteBuffer 的現(xiàn)成實現(xiàn), 實現(xiàn)上述類似的文件復制功能, 通常要自己創(chuàng)建管理緩沖區(qū). C++ 里讀寫文件通常用 FileRead(), FileWrite() 函數(shù), 在讀/寫的時候, 可以直接指定讀/寫的數(shù)據(jù)長度, 相比下顯得 直觀方便些, 但 JDK 這個 ByteBuffer 的方式, 確實更方便好用.
ByteBuffer 作為繼承自 Buffer 的抽象類, 實現(xiàn)了對 Byte 型緩沖的管理, 同時 JDK 里還有對應其他數(shù)據(jù)類型的 繼承自 Buffer 的抽象類, 分別實現(xiàn)對應類型的緩沖管理. 這種設計減少了編程時的工作. 如果在 C++ 中, 調(diào)用 讀/寫函數(shù)時, 還需要考慮傳入數(shù)據(jù)的類型, 通常用傳入 sizeof(數(shù)據(jù)類型) 的方式指定, 除了函數(shù)調(diào)用時增加耗 費外, 靈活性也更差些.
反過來再想, 為什么 C 要用這種方式? 個人認為, 這是 C 標準庫的實現(xiàn)方式, 因為在不同 OS 平臺上, 對文件和 設備的訪問方法在系統(tǒng)層不一定相同, 同時硬件平臺(主要是 CPU)上基本數(shù)據(jù)類型寬度也有可能不同, 標準庫通過 sizeof 這個宏在編譯時才能確定數(shù)據(jù)寬度, 所以標準 C 代碼通常可以在不同平臺上重新編譯.
再想 C++ 為什么要用這種方式? 首先, C++ 里沿用 C 標準庫的模式是可以理解的, 其次, 也許只是 C++ 標準庫 里沒有類似的設計, 說不定早就有第三方通過模板實現(xiàn)的了.
個人認為, ByteBuffer 在實現(xiàn)上, 可以算是一種數(shù)據(jù)結構, 在類設計上, 可以算是一種設計模式了.
評論
# re: 對 java.nio.ByteBuffer 的理解[未登錄] 回復 更多評論
2014-04-07 11:21 by
1
# re: 對 java.nio.ByteBuffer 的理解[未登錄] 回復 更多評論
2014-04-07 11:21 by
11
# re: 對 java.nio.ByteBuffer 的理解[未登錄] 回復 更多評論
2014-04-07 11:21 by
1
# re: 對 java.nio.ByteBuffer 的理解[未登錄] 回復 更多評論
2014-04-07 11:22 by
12
|