|
Posted on 2011-05-18 10:53 Astro.Qi 閱讀(412) 評論(4) 編輯 收藏 所屬分類: Java
今晚用到 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 處:
當前位置: 這個可以直觀地理解為緩沖區中的當前數據指針, 或是 SQL 中的游標, 記為 curPointer. 限制: 這個可以理解成實際操作的緩沖區段的結束標記, 記為 endPointer. 反轉: 這個完全是對 flip 這個詞不負責的翻譯, 如果參照 DirectX 里的 flip() 而譯為翻轉/翻頁, 那就好理解得多, 就像寫信/看信, 寫/看完一頁后, 翻到下一頁, 眼睛/筆從頁底重新移回頁首. 這個翻轉背后的操作其實就是 "把 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 處到緩沖區實際結束處的 5 個未初始化的字節. * 調用一次 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++ 的簡單比較: 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 在實現上, 可以算是一種數據結構, 在類設計上, 可以算是一種設計模式了.
評論
# 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
|