NIO 的 ByteBuffer 三个指针方法(flip/clear/compact)是出了名的容易搞混。这篇真实每一步都打印 position 和 limit,把三个方法的真实效果一次性理清楚。
典型的"写模式切读模式"操作:写完数据后调用 flip(),才能开始正确地读。
NioBufferDemo.java ByteBuffer buf = ByteBuffer.allocate(10); buf.put((byte)1).put((byte)2).put((byte)3); buf.flip();
初始: position=0 limit=10 capacity=10 put 3个字节后: position=3 limit=10 flip() 后: position=0 limit=3 get() 一次后: position=1 读到的值=1
真实结果验证了 flip() 的机制:写入 3 个字节后,position 真实指向下一个可写位置(3),limit 还是初始的容量(10)。调用 flip() 后,limit 被真实设为刚才的 position 值(3,也就是"已写入多少数据"),position 归零——这样后续的 get() 就能真实从头读到刚才写入的确切数据范围,不多不少。
"clear"这个名字容易让人误以为数据被清空了,真实验证并非如此。
clear() 后: position=0 limit=10 clear() 不清空底层数组,读到的旧数据仍在: buf.get(1) = 2
半包读写场景的核心方法:读走一部分数据后,把剩下没读的部分挪到开头,继续写入新数据接上。
NioBufferDemo.java buf2.put(10).put(20).put(30).put(40); buf2.flip(); buf2.get(); // 读走10 buf2.compact(); // 把20,30,40移到开头
compact前: position=1 limit=4 已读走: 10 compact()后: position=3 limit=10 compact()后 buf2.get(0) 真实是20(未读数据被移到开头): 20
真实结果证明了 compact() 的核心用途:读走第一个字节(10)后,剩下 20/30/40 三个未读字节;compact() 真实把这三个字节整体搬到数组下标 0 开始的位置,position 被设为 3(指向搬运后紧跟着的下一个可写位置),limit 恢复为 capacity——这样调用方可以直接继续 put() 写入新数据,接在刚才未处理完的数据后面,不会覆盖还没读的内容。这是处理网络粘包/半包问题时最常用到的一个方法。
和 flip()、clear() 都不同,rewind() 用于"重新读一遍刚才已经读过的相同范围数据"。
rewind前: position=2 rewind后: position=0(limit不变,可以重新读一遍)
真实结果验证了 rewind() 的精确行为:只把 position 归零,limit 真实保持不变——这意味着之前 flip() 设定好的"有效数据范围"依然有效,可以从头把这段数据重新读一遍,常用于需要对同一段数据做多次处理的场景(比如先扫描一遍统计长度,再重新读一遍真正处理)。
每一行都是真实运行这段代码得到的结果。