首页 > 代码库 > java ByteBuffer flip()和limit()的理解, 转载的, 从里面理解到flip()的作用, 想象一下 老式打字机

java ByteBuffer flip()和limit()的理解, 转载的, 从里面理解到flip()的作用, 想象一下 老式打字机

先列点代码片段: 
// ... 
// 
// 此段代码功能为从 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(); 

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 字节) 
------------------- 
------------------- 


* 然后填充 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() 两个方法.

java ByteBuffer flip()和limit()的理解, 转载的, 从里面理解到flip()的作用, 想象一下 老式打字机