posts - 40, comments - 58, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          對 java.nio.ByteBuffer 的理解

          Posted on 2011-05-18 10:53 Astro.Qi 閱讀(417) 評論(4)  編輯  收藏 所屬分類: Java

          今晚用到 ByteBuffer, 我跟 joy 都是初學(xué) java, 文檔里的中文翻譯實(shí)在是看他母親不懂, 暈了半天, 作了幾個(gè)測試, 終于把這個(gè)類的用法搞清楚了, 順便臆想了哈其工作原理.

          先列點(diǎn)代碼片段:
          // ...
          //
          // 此段代碼功能為從 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é), 耗時(shí) " + ( t2 - t1 + 1 ) + " ms." );
          ...

          ----------------------------------------------------------------------------------------------------

          SDK 文檔里對 ByteBuffer 的說明為:

          public abstract class ByteBuffer
          extends Buffer
          implements Comparable <ByteBuffer>

          這說明 ByteBuffer 是繼承于 Buffer 的抽象類, 實(shí)現(xiàn)了兩個(gè)接口.

          行3 通過 allocate() 分配了一塊 1024 字節(jié)的緩沖區(qū), 并返回一個(gè) ByteBuffer 對象. (抽象類不能直接 new)
          行6 fcin.read() 將數(shù)據(jù)讀入到 buff. 此處的 read() 是 FileChannel 類的一個(gè)虛函數(shù).
          行8 buff.flip() 這個(gè)調(diào)用就是開頭一直無法理解的部分.

          ----------------------------------------------------------------------------------------------------

          SDK 文檔里的對 flip() 的說明是:

          public final Buffer flip()
          反轉(zhuǎn)此緩沖區(qū)。首先對當(dāng)前位置設(shè)置限制,然后將該位置設(shè)置為零。如果已定義了標(biāo)記,則丟棄該標(biāo)記。
          當(dāng)將數(shù)據(jù)從一個(gè)地方傳輸?shù)搅硪粋€(gè)地方時(shí),經(jīng)常將此方法與 compact 方法一起使用。

          我最終的理解是: 文檔翻譯得太差了, 把不應(yīng)該翻譯的內(nèi)容也譯成了中文, 所以反而不容易理解.
          關(guān)鍵就在以下 2 處:

          當(dāng)前位置: 這個(gè)可以直觀地理解為緩沖區(qū)中的當(dāng)前數(shù)據(jù)指針, 或是 SQL 中的游標(biāo), 記為 curPointer.
          限制: 這個(gè)可以理解成實(shí)際操作的緩沖區(qū)段的結(jié)束標(biāo)記, 記為 endPointer.
          反轉(zhuǎn): 這個(gè)完全是對 flip 這個(gè)詞不負(fù)責(zé)的翻譯, 如果參照 DirectX 里的 flip() 而譯為翻轉(zhuǎn)/翻頁, 那就好理解得多, 就像寫信/看信, 寫/看完一頁后, 翻到下一頁, 眼睛/筆從頁底重新移回頁首.
          這個(gè)翻轉(zhuǎn)背后的操作其實(shí)就是 "把 endPointer 定位到 curPointer 處, 并把 curPointer 設(shè)為 0".

          關(guān)于標(biāo)記, 在這里不涉及. 下一句說到常與 compact 方法一起使用, 是可以想像的, 因?yàn)?compact 方法對數(shù)據(jù)進(jìn)
          行了壓縮, 有效數(shù)據(jù)的真實(shí)長度發(fā)生了變化, 肯定需要用 flip 重新定位結(jié)束標(biāo)記.

          在填充, 壓縮等數(shù)據(jù)操作時(shí), curPointer 估計(jì)都是自動更新了位置的, 總是指向最后一個(gè)有效數(shù)據(jù), 所以每次調(diào)
          用 flip() 后, endPointer 就指向了有效數(shù)據(jù)的結(jié)尾, 而 curPointer 指向了 0 (緩沖起始處).

          舉個(gè)圖例:
          (c 和 e 分別代表 curPointer 和 endPointer 兩個(gè)指針)

          * 先是一個(gè)空的 ByteBuffer (大小為 10 字節(jié))
          -------------------
          -------------------
          c
          e


          * 然后填充 5 字節(jié)數(shù)據(jù)
          -------------------
          0 1 2 3 4
          -------------------
          e           c
          此時(shí), endPointer 尚在 0 處, curPointer 移到了數(shù)據(jù)結(jié)尾.
          經(jīng)測試, 此時(shí)若取數(shù)據(jù), 將得到 5 個(gè)字節(jié), 內(nèi)容通常為 0 (也有可能是未知), 因?yàn)閷?shí)際上取到的是從 c 處到緩沖
          區(qū)實(shí)際結(jié)束處的 5 個(gè)未初始化的字節(jié).

          * 調(diào)用一次 flip() 后
          -------------------
          0 1 2 3 4
          -------------------
          c           e
          此時(shí), endPointer 先被移到 curPointer, 然后 curPointer 移到 0.
          通過測試可見, ByteBuffer 取數(shù)據(jù)時(shí), 是從 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 則取到緩沖區(qū)結(jié)束.


          再看上面代碼的關(guān)鍵片段, 行 8 處調(diào)用 flip() 即有兩個(gè)作用, 一是將 curPointer 移到 0, 二是將 endPointer 移到有效數(shù)據(jù)結(jié)尾.

          此行可由以下兩行代替:
          buff.limit( buff.position());
          buff.position( 0 );

          可見對其工作原理的理解, 應(yīng)該是正確的.

          ----------------------------------------------------------------------------------------------------

          總結(jié)如下:
          1. put 數(shù)據(jù)時(shí), 不會自動清除緩沖區(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 只是為文中方便描述命名的, 實(shí)際分別對應(yīng)到 ByteBuffer.position() 和 ByteBuffer.limit() 兩個(gè)方法.

          ----------------------------------------------------------------------------------------------------

          疑惑:
          curPointer 是用 ByteBuffer.position() 取值, 用 ByteBuffer.position( int ) 賦值, 不知道 JDK 為什么要用多態(tài)來實(shí)現(xiàn)這兩個(gè)功能, 按我的想法, 設(shè)計(jì)成 getPosition(), setPosition() 不是要好看好記得多啊.

          ----------------------------------------------------------------------------------------------------

          跟 C++ 的簡單比較:
          C++ 里面沒有類似 ByteBuffer 的現(xiàn)成實(shí)現(xiàn), 實(shí)現(xiàn)上述類似的文件復(fù)制功能, 通常要自己創(chuàng)建管理緩沖區(qū). C++ 里讀寫文件通常用 FileRead(), FileWrite() 函數(shù), 在讀/寫的時(shí)候, 可以直接指定讀/寫的數(shù)據(jù)長度, 相比下顯得
          直觀方便些, 但 JDK 這個(gè) ByteBuffer 的方式, 確實(shí)更方便好用.

          ByteBuffer 作為繼承自 Buffer 的抽象類, 實(shí)現(xiàn)了對 Byte 型緩沖的管理, 同時(shí) JDK 里還有對應(yīng)其他數(shù)據(jù)類型的
          繼承自 Buffer 的抽象類, 分別實(shí)現(xiàn)對應(yīng)類型的緩沖管理. 這種設(shè)計(jì)減少了編程時(shí)的工作. 如果在 C++ 中, 調(diào)用
          讀/寫函數(shù)時(shí), 還需要考慮傳入數(shù)據(jù)的類型, 通常用傳入 sizeof(數(shù)據(jù)類型) 的方式指定, 除了函數(shù)調(diào)用時(shí)增加耗
          費(fèi)外, 靈活性也更差些.

          反過來再想, 為什么 C 要用這種方式? 個(gè)人認(rèn)為, 這是 C 標(biāo)準(zhǔn)庫的實(shí)現(xiàn)方式, 因?yàn)樵诓煌?OS 平臺上, 對文件和
          設(shè)備的訪問方法在系統(tǒng)層不一定相同, 同時(shí)硬件平臺(主要是 CPU)上基本數(shù)據(jù)類型寬度也有可能不同, 標(biāo)準(zhǔn)庫通過
          sizeof 這個(gè)宏在編譯時(shí)才能確定數(shù)據(jù)寬度, 所以標(biāo)準(zhǔn) C 代碼通常可以在不同平臺上重新編譯.

          再想 C++ 為什么要用這種方式? 首先, C++ 里沿用 C 標(biāo)準(zhǔn)庫的模式是可以理解的, 其次, 也許只是 C++ 標(biāo)準(zhǔn)庫
          里沒有類似的設(shè)計(jì), 說不定早就有第三方通過模板實(shí)現(xiàn)的了.

          個(gè)人認(rèn)為, ByteBuffer 在實(shí)現(xiàn)上, 可以算是一種數(shù)據(jù)結(jié)構(gòu), 在類設(shè)計(jì)上, 可以算是一種設(shè)計(jì)模式了.

          評論

          # re: 對 java.nio.ByteBuffer 的理解[未登錄]  回復(fù)  更多評論   

          2014-04-07 11:21 by 1
          1

          # re: 對 java.nio.ByteBuffer 的理解[未登錄]  回復(fù)  更多評論   

          2014-04-07 11:21 by 1
          11

          # re: 對 java.nio.ByteBuffer 的理解[未登錄]  回復(fù)  更多評論   

          2014-04-07 11:21 by 1
          1

          # re: 對 java.nio.ByteBuffer 的理解[未登錄]  回復(fù)  更多評論   

          2014-04-07 11:22 by 1
          12
          主站蜘蛛池模板: 富顺县| 钟山县| 汝州市| 五原县| 墨玉县| 潼南县| 沙田区| 陆良县| 疏附县| 辉南县| 墨脱县| 泸西县| 南汇区| 九台市| 蚌埠市| 通河县| 济阳县| 崇礼县| 鞍山市| 乌鲁木齐县| 渭南市| 潮安县| 永嘉县| 拉萨市| 长垣县| 沽源县| 宣威市| 承德县| 武宁县| 扶绥县| 定州市| 广丰县| 额敏县| 永康市| 永善县| 石渠县| 扎囊县| 克拉玛依市| 洛阳市| 上饶市| 宁阳县|