基于本文回答
0
评论

Buffer 的 flip()、clear() 和 compact() 方法分别有什么作用?

在 Java NIO 中,Buffer(缓冲区)是用于与通道(Channel)进行交互的内存块。理解 flip()clear()compact() 这三个方法,关键在于理解 Buffer 内部的三个核心指针:

  • position(位置):当前读取或写入的位置。
  • limit(界限):缓冲区中当前有效数据的边界(写模式下等于 capacity,读模式下表示最多能读多少数据)。
  • capacity(容量):缓冲区的最大容量,在创建时设定,不可改变。

以下是这三个方法的详细作用、内部指针变化以及适用场景:


1. flip() —— 模式翻转(写模式 -> 读模式)

  • 核心作用
    将 Buffer 从写模式切换到读模式。准备读取刚刚写入 Buffer 的数据。
  • 内部指针变化
    1. limit 设置为当前的 position(表示最多只能读到刚才写到的位置)。
    2. position 重置为 0(从头开始读)。
    3. 清除标记(mark)。
  • 代码等价于
    java
    limit = position;
    position = 0;
    mark = -1;
  • 使用场景
    向 Buffer 写入数据后(例如通过 channel.read(buffer)buffer.put()),如果你想从 Buffer 中读取数据(例如通过 channel.write(buffer)buffer.get()),必须先调用 flip()

2. clear() —— 清空缓冲区(初始化,准备重新写)

  • 核心作用
    将 Buffer 清空,准备重新写入数据。
  • 注意:它并没有真正擦除内存中的数据,只是重置了指针,让后续的写入操作直接覆盖旧数据。
  • 内部指针变化
    1. position 重置为 0
    2. limit 设置为 capacity(恢复到最大可写状态)。
    3. 清除标记(mark)。
  • 代码等价于
    java
    position = 0;
    limit = capacity;
    mark = -1;
  • 使用场景
    当 Buffer 中的数据已经全部读完,或者你不再关心未读完的数据,想要重新开始向 Buffer 写入数据时使用。

3. compact() —— 压缩缓冲区(未读完时准备写)

  • 核心作用
    如果 Buffer 中的数据只读了一部分,你还想往里面写数据,但又不想丢失未读的数据。compact() 会将所有未读的数据复制到 Buffer 的头部,然后将指针定位到未读数据之后,以便继续写入。
  • 内部指针变化
    1. 将所有未读的数据(positionlimit 之间的数据)复制到 Buffer 的起始位置(从索引 0 开始)。
    2. position 设置为未读数据的长度(即 limit - position)。
    3. limit 设置为 capacity
  • 举个例子
    假设 Capacity 是 6。你写入了 5 个字节(pos=5, lim=6),然后 flip()(pos=0, lim=5)。
    你读取了 2 个字节(pos=2, lim=5)。此时还有 3 个字节未读。
    调用 compact() 后:
    • 未读的 3 个字节被复制到索引 0, 1, 2 的位置。
    • position 变为 3(指向下一个可写的位置)。
    • limit 变为 6(最大容量)。
    • 现在你可以继续写入,且未读的数据被保留了。
  • 使用场景
    读写交替的场景中(特别是网络通信),经常会出现一次没有读完 Buffer 中所有数据的情况。此时用 compact() 腾出空间继续写,比用 clear()(会导致数据丢失)或频繁创建新 Buffer 效率高得多。

快速对比总结

方法 目的 position 的变化 limit 的变化 适用场景
flip() 写模式 -> 读模式 变为 0 变为之前的 position 准备从 Buffer 读取刚才写入的数据。
clear() 初始化,完全清空 变为 0 变为 capacity 数据已读完,准备重新写入全新数据。
compact() 压缩空间,部分清空 变为未读数据的长度 变为 capacity 数据没读完,保留未读数据并准备继续写。

典型工作循环

在实际代码中,它们通常这样配合使用:

java
DoubleBuffer buffer = DoubleBuffer.allocate(1024);

while (channel.read(buffer) != -1) {
    buffer.flip(); // 1. 切换到读模式,准备把数据写到通道/处理数据
    
    while(buffer.hasRemaining()){
        System.out.print(buffer.get()); // 读取数据
    }
    
    buffer.clear(); // 2. 读完了,完全清空指针,准备下一次写入
}

如果读的时候可能读不完:

java
// 读了一部分...
if (hasUnreadData) {
    buffer.compact(); // 3. 没读完,保留未读数据,准备继续写
} else {
    buffer.clear();   // 读完了,直接清空
}
右滑查看面试常问