← 返回题目列表

ByteBuffer 的 position、limit、capacity 是什么?flip、clear、compact 有什么区别?

高频 中等 第 4 / 22 题 更新于 2026/07/25
ByteBufferpositionlimitflipcompact

简化版

capacity 是缓冲区总容量,position 是下一个要读或写的位置,limit 是当前模式下不能越过的边界。flip() 把已写入的数据切换为可读;clear() 丢弃未读状态,准备重写整个 Buffer;compact() 先把未读数据移到开头,再从其后继续写。

详细版

Buffer 始终满足:

0 <= position <= limit <= capacity
  • capacity:创建后固定的容量。
  • position:相对 get() / put() 下一次操作的索引,操作后会向后移动。
  • limit:第一个不允许被当前读写操作访问的索引。
  • mark:可选的位置书签,reset() 可将 position 回到 mark。

典型流程是:创建 Buffer 后处于写模式;向其中写数据;调用 flip();从位置 0 读到原写入位置;读完后调用 clear() 或未读完时调用 compact()

完整版教学

一、用一个 8 字节 Buffer 理解状态

ByteBuffer buffer = ByteBuffer.allocate(8);

刚创建时:

position = 0, limit = 8, capacity = 8

写入 3 个字节后:

position = 3, limit = 8, capacity = 8

这时如果直接调用相对 get(),它会从位置 3 开始读,而不是读刚写入的前 3 个字节。flip() 完成两件事:

limit = position;  // limit = 3
position = 0;

于是可读区间变成 [0, 3)。这就是“翻转”的含义:不是翻转数据,而是改变边界,把当前 Buffer 从填充模式转成消费模式。

二、clear 不会把字节清零

clear() 的结果是:

position = 0
limit = capacity
mark = undefined

它只重置状态指针,原来的字节可能仍在内存中,只是之后的 put() 可以覆盖它们。因此 clear() 不是安全擦除,也不应用来保证敏感数据从内存中消失。

三、compact 为什么用于半包和部分写

假设读模式下 position = 3limit = 8,说明 [3, 8) 还没消费。compact() 会把这 5 个字节复制到 Buffer 开头,然后设置:

position = 5
limit = capacity

程序可以继续从位置 5 写入新数据,原未处理数据不会丢失。网络协议解码时,一次 read() 可能只得到半条消息,这时就应保留已到达的部分,等下一批字节拼接。

int count;
while ((count = channel.read(buffer)) > 0) {
    buffer.flip();
    decodeCompleteFrames(buffer); // position 停在未解析数据的开头
    buffer.compact();              // 保留未解析数据
}

四、rewind、mark 和绝对访问

  • rewind():把 position 设为 0,保留 limit,适合重新读一遍当前数据。
  • mark() / reset():保存并回到一个位置。当 position 或 limit 被调整到 mark 之前,mark 会失效。
  • get(index) / put(index, value):绝对访问不改变 position,适合查看协议头或回填长度字段。
  • remaining() 等于 limit - positionhasRemaining() 用来判断当前还有没有可读或可写空间。

五、一个常见的部分写模式

buffer.flip();
while (buffer.hasRemaining()) {
    int written = channel.write(buffer);
    if (written == 0) {
        // 非阻塞 Channel 暂时不可写,保留 Buffer 状态等 OP_WRITE
        break;
    }
}

if (buffer.hasRemaining()) {
    buffer.compact();
} else {
    buffer.clear();
}

真正的非阻塞服务器通常会把待发 Buffer 保存在连接上下文中,等 OP_WRITE 就绪后继续写。绝不能在未写完时就 clear(),否则剩余数据会被新数据覆盖。

六、视图、切片与字节序

duplicate() 创建共享同一数据但拥有独立 position、limit、mark 的视图;slice() 则从当前 position 到 limit 创建子视图,子视图的 position 从 0 开始。它们通常不复制底层字节,因此修改一个可写视图会影响其他共享视图,不能把它们当作数据快照。

多字节整数还受 ByteOrder 影响。网络协议常用大端序,ByteBuffer 默认也是 BIG_ENDIAN;如果协议规定小端序,就必须显式调用 order(ByteOrder.LITTLE_ENDIAN)。例如字节 01 02 03 04 按大端读取为 0x01020304,按小端读取则为 0x04030201

操作数据是否复制新视图初始范围是否共享修改
duplicate()原 Buffer 的完整状态范围
slice()原 position 到 limit
手动复制到新 Buffer由新 Buffer 决定

易错点:Buffer 的“状态独立”不等于“数据独立”;duplicate 和 slice 的游标各走各的,但底层内容仍可能是同一块内存。

七、常见误区与追问

  • 误区:flip() 会把 Buffer 中的字节倒序。 它只把旧 position 设为新 limit,再把 position 归零。
  • 误区:clear() 会将旧数据全部置零。 它只重置边界,旧字节仍可能留在底层存储中并等待覆盖。
  • 误区:一次 Channel read() 就能填满 Buffer。 返回值取决于可用数据、EOF 和通道模式,可能小于 remaining,非阻塞时还可能为 0。
  • 误区:一次 Channel write() 一定写完整个 Buffer。 写操作可能只推进部分 position,必须检查 hasRemaining()
  • 追问:为什么半包解析后用 compact() 而不是 clear() compact 会把 [position, limit) 的未消费字节搬到开头,为下一批数据腾出连续空间。
  • 追问:绝对 get(index) 会推进 position 吗? 不会,它适合查看协议头;相对 get 才会推进游标。
  • 追问:slice() 是不是线程安全副本? 不是,它共享内容,且 ByteBuffer 本身通常不保证并发访问安全,所有权仍需协调。

八、加强记忆

Buffer 的核心是 positionlimit 之前移动,capacity 则是固定上限。写完准备读用 flip(),数据全部消费后用 clear(),还有未读数据时用 compact() 保留剩余再继续写。