bigywater.tech
Java 内部原理 · 67

ByteBuffer flip/clear/compact 真实验证:position 和 limit 到底怎么变

NIO 的 ByteBuffer 三个指针方法(flip/clear/compact)是出了名的容易搞混。这篇真实每一步都打印 position 和 limit,把三个方法的真实效果一次性理清楚。

clear() 真实不清数据
真实验证:clear() 后 position/limit 被重置,但底层数组数据真实没有被清空,get(1) 依然能读到旧值
compact() 真实搬运未读数据
真实验证:compact() 把尚未读取的数据真实移动到 buffer 开头,不是简单清空,读走的部分被真实覆盖

上一篇:String.format() 真实开销

真实验证:flip() 把 limit 设为当前 position

典型的"写模式切读模式"操作:写完数据后调用 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"这个名字容易让人误以为数据被清空了,真实验证并非如此。

真实实测
clear() 后: position=0 limit=10
clear() 不清空底层数组,读到的旧数据仍在: buf.get(1) = 2
真实机制:重置指针,不动数据。clear() 真实做的事只是把 position 归零、limit 恢复成 capacity,让 buffer 看起来"准备好接收新的写入"——但底层数组里原来的字节内容真实原封不动。用 buf.get(1)(绝对位置读取,不影响 position)真实还能读到之前写入的值 2。这解释了为什么"clear 之后忘记覆盖全部旧数据就直接按 limit=capacity 去读"是一个真实存在的 bug 来源——读到的可能是上一轮残留的脏数据。

真实验证:compact() 真实搬运未读完的数据

半包读写场景的核心方法:读走一部分数据后,把剩下没读的部分挪到开头,继续写入新数据接上。

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() 写入新数据,接在刚才未处理完的数据后面,不会覆盖还没读的内容。这是处理网络粘包/半包问题时最常用到的一个方法。

真实验证:rewind() 只回退 position,不动 limit

和 flip()、clear() 都不同,rewind() 用于"重新读一遍刚才已经读过的相同范围数据"。

真实实测
rewind前: position=2
rewind后: position=0(limit不变,可以重新读一遍)

真实结果验证了 rewind() 的精确行为:只把 position 归零,limit 真实保持不变——这意味着之前 flip() 设定好的"有效数据范围"依然有效,可以从头把这段数据重新读一遍,常用于需要对同一段数据做多次处理的场景(比如先扫描一遍统计长度,再重新读一遍真正处理)。

分步演示:一个 ByteBuffer 的指针是怎么真实变化的

每一行都是真实运行这段代码得到的结果。

position/limit/capacity 三个指针真实是怎么随 put/flip/get/clear/compact 一步步移动的
这一步新验证的状态

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,四个方法的 position/limit 变化重复运行完全一致(纯确定性逻辑,不涉及并发或GC)。
    • 和ThreadLocal那篇一样属于"容易被文档描述得抽象、真实跑一遍才彻底理解"的 API,NIO Buffer 的指针操作是另一个典型代表。
    • 没有涉及:DirectByteBuffer(堆外内存)和 HeapByteBuffer 的真实性能差异、Buffer 的 mark() 和 reset() 配合使用的真实场景——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号