slice()创建共享底层数组的子缓冲区,不复制数据;修改子缓冲区内容会直接影响原buffer对应位置,二者position、limit等元数据独立,但底层存储共享。

slice() 创建的是共享底层数组的子缓冲区
调用 slice() 不会复制数据,而是基于当前缓冲区的 position 到 limit 范围,新建一个独立的 ByteBuffer 实例。新缓冲区的 capacity 等于原缓冲区的 remaining(),position 固定为 0,limit 等于其 capacity。最关键的是:它与原缓冲区共享同一块底层字节数组(array() 返回相同引用),但各自维护独立的 position、limit、mark 和 order。
这意味着:
- 修改子缓冲区的内容(如
put())会直接影响原缓冲区对应位置的数据 - 子缓冲区的
flip()、rewind()不会影响原缓冲区的指针状态 - 原缓冲区后续调用
compact()或clear()会破坏子缓冲区的视图有效性(因为可能移动或重置指针)
为什么 slice() 后的 put() 会改写原始数据
这是共享底层数组的直接结果,不是 bug。例如:
ByteBuffer bb = ByteBuffer.allocate(10); bb.put((byte)1).put((byte)2).put((byte)3); // position=3 ByteBuffer sub = bb.slice(); // sub.capacity==7, sub.position==0, sub.limit==7 sub.put((byte)99); // 修改 bb[3] 的值为 99
此时 bb.get(3) 就会返回 99。常见误判是以为 slice() 类似 Arrays.copyOfRange(),但它不是——它更像 Go 中的 slice 操作或 Python 的 memoryview。
如果你需要真正隔离的数据副本,请显式调用 sub.duplicate().array() 再构造新缓冲区,或用 ByteBuffer.wrap(sub.array(), sub.arrayOffset(), sub.remaining())(注意:仅对 heap buffer 有效;direct buffer 不支持 array())。
slice() 在协议解析中如何安全使用
典型场景是解析网络包:一个大缓冲区先读入完整帧,再按字段切片处理。这时必须保证在所有子视图使用完毕前,原缓冲区不被回收、不清空、不 compact。
- 不要在
slice()后立即对原缓冲区调用clear()—— 这会让子缓冲区的limit仍指向旧范围,但原缓冲区已准备复用,逻辑错乱 - 避免在子缓冲区活跃时调用原缓冲区的
put()(除非你明确知道写入位置不重叠) - 如果需多次解析不同字段,推荐一次性切片并保存多个子缓冲区引用,而不是反复调用
slice()+position()移动原缓冲区 - 对 direct buffer,
slice()依然共享 native memory,因此同样存在写覆盖风险,且无法通过array()检查
duplicate() vs slice():何时该选哪个
duplicate() 复制缓冲区元数据(position、limit、mark、order),但共享整个底层存储;slice() 只共享从当前 position 到 limit 的区间,且新缓冲区 position 固定为 0。
选择依据很实际:
- 要操作原缓冲区的「当前剩余部分」并希望新缓冲区从头开始计数(比如解码一个 TLV 字段),用
slice() - 要临时保存当前读写状态以便之后恢复(比如预读失败后回退),用
duplicate() - 两者都不能防止对方修改数据——若需只读保护,必须用
asReadOnlyBuffer()显式包装
最易忽略的一点:slice() 出来的缓冲区调用 isDirect() 结果和原缓冲区一致,但它的 arrayOffset() 是原缓冲区的 arrayOffset() + position,这个偏移量在手动做 array 访问时必须参与计算,否则越界。










