ByteBuffer.compact()的核心作用是读取未完成时安全切回写模式:将position到limit间的未读数据复制到缓冲区开头,position设为复制字节数,limit设为capacity,从而保留残留数据并腾出尾部空间接收新数据。

ByteBuffer.compact() 的核心作用,是在读取未完成时安全地切换回写模式,同时保留尚未处理的残留数据。它不是清空或重置,而是“前移保底”——把还没读完的那部分字节挪到缓冲区开头,腾出尾部空间接收新数据,避免丢包或覆盖。
compact() 什么时候必须用?
典型场景是网络 I/O 中遇到半包:一次 read() 只收到消息的一部分(比如协议头已解析,但消息体还没收全),而此时连接又准备继续写入新数据(如响应、心跳或下一条请求)。这时:
- 不能调 flip() —— 后半截数据会被跳过,下次读不到;
- 不能调 clear() —— 已读的前半截和未读的后半截全被逻辑丢弃;
- 必须调 compact() —— 把未读区域(position 到 limit)拷贝到起始位置,让缓冲区“收缩待续”。
compact() 具体做了什么?
假设一个容量为 1024 的 ByteBuffer,当前状态是:
- 已写入 800 字节(position = 800,limit = 1024);
- 你调用 get() 读了前 500 字节(position = 500,limit = 1024);
- 剩下 300 字节(下标 500~799)还没处理,但连接又要收新数据。
执行 buffer.compact() 后:
- 将下标 500~799 的 300 字节复制到下标 0~299;
- position 被设为 300(指向刚复制完的末尾后一位);
- limit 恢复为 capacity(1024),表示现在最多还能写入 724 字节;
- mark(如果存在)被丢弃,已读区域(0~499)不再受保护,但数据已不在有效范围内。
为什么 compact() 后不能直接读?
因为 compact() 的设计目标是“为下一次写做准备”,不是为读。它移动数据后,position 指向的是可写起点,limit 是最大容量,缓冲区仍处于写模式。若你想立刻读这 300 字节,必须再调一次 flip():
- flip() 将 limit 设为当前 position(300),position 设为 0;
- 之后 get() 才能从头读出这 300 字节;
- 读完再 compact() 或 clear(),视业务是否还有残留需保留。
实际编码中的关键注意点
在 TCP 粘包/半包处理循环中,compact() 常与 read/write 配合使用:
- 每次 write() 返回值小于 buffer.remaining(),说明只发出了部分数据 → 紧跟 compact();
- 避免在完整读完后误调 compact(),白白触发内存拷贝,浪费 CPU;
- 大缓冲区(如 64KB+)频繁 compact() 会增加 GC 压力,可考虑增大初始容量或改用 DirectByteBuffer;
- 多线程不可共享同一 ByteBuffer,compact() 非原子操作,须自行同步或使用 ThreadLocal。

















