先列点代码片段:
// ...
//
// 此段代码功能为从 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 是继承于 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() 两个方法.