Buffer 的 flip()、clear() 和 compact() 方法分别有什么作用?
在 Java NIO 中,Buffer(缓冲区)是用于与通道(Channel)进行交互的内存块。理解 flip()、clear() 和 compact() 这三个方法,关键在于理解 Buffer 内部的三个核心指针:
position(位置):当前读取或写入的位置。limit(界限):缓冲区中当前有效数据的边界(写模式下等于 capacity,读模式下表示最多能读多少数据)。capacity(容量):缓冲区的最大容量,在创建时设定,不可改变。
以下是这三个方法的详细作用、内部指针变化以及适用场景:
1. flip() —— 模式翻转(写模式 -> 读模式)
- 核心作用:
将 Buffer 从写模式切换到读模式。准备读取刚刚写入 Buffer 的数据。 - 内部指针变化:
- 将
limit设置为当前的position(表示最多只能读到刚才写到的位置)。 - 将
position重置为0(从头开始读)。 - 清除标记(
mark)。
- 将
- 代码等价于:java
limit = position; position = 0; mark = -1; - 使用场景:
向 Buffer 写入数据后(例如通过channel.read(buffer)或buffer.put()),如果你想从 Buffer 中读取数据(例如通过channel.write(buffer)或buffer.get()),必须先调用flip()。
2. clear() —— 清空缓冲区(初始化,准备重新写)
- 核心作用:
将 Buffer 清空,准备重新写入数据。 - 注意:它并没有真正擦除内存中的数据,只是重置了指针,让后续的写入操作直接覆盖旧数据。
- 内部指针变化:
- 将
position重置为0。 - 将
limit设置为capacity(恢复到最大可写状态)。 - 清除标记(
mark)。
- 将
- 代码等价于:java
position = 0; limit = capacity; mark = -1; - 使用场景:
当 Buffer 中的数据已经全部读完,或者你不再关心未读完的数据,想要重新开始向 Buffer 写入数据时使用。
3. compact() —— 压缩缓冲区(未读完时准备写)
- 核心作用:
如果 Buffer 中的数据只读了一部分,你还想往里面写数据,但又不想丢失未读的数据。compact()会将所有未读的数据复制到 Buffer 的头部,然后将指针定位到未读数据之后,以便继续写入。 - 内部指针变化:
- 将所有未读的数据(
position到limit之间的数据)复制到 Buffer 的起始位置(从索引0开始)。 - 将
position设置为未读数据的长度(即limit - position)。 - 将
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(); // 读完了,直接清空
}
右滑查看面试常问