Buffer缓冲区
Buffer 是 NIO 的核心。传统 IO 中数据从流里来,NIO 中数据必须先进入缓冲区,程序再从缓冲区读取或写入。
# 1. Buffer 的本质
Buffer 本质上是一块内存区域,用于临时存放数据。
ByteBuffer buffer = ByteBuffer.allocate(1024);
常见 Buffer:
ByteBufferCharBufferIntBufferLongBufferDoubleBufferMappedByteBuffer
网络和文件 IO 中最常用的是 ByteBuffer。
# 2. 三个核心属性
Buffer 有三个非常重要的属性:
capacity:容量,缓冲区最大能存多少数据。position:当前位置,下一次读或写的位置。limit:边界,读写不能超过的位置。
还有一个可选属性:
mark:标记位置,可通过reset()回到标记点。
# 3. 写模式和读模式
Buffer 最容易让初学者困惑的是模式切换。
写入数据:
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put((byte) 1);
buffer.put((byte) 2);
此时 position 向后移动,表示写到了哪里。
读取前必须调用 flip():
buffer.flip();
while (buffer.hasRemaining()) {
byte b = buffer.get();
}
flip() 的作用是:
- 把
limit设置为当前position。 - 把
position设置为 0。
这样 Buffer 就从写模式切换到读模式。
# 4. clear 和 compact
读取完成后,如果要继续写入,可以调用 clear():
buffer.clear();
clear() 不会真正清空内存数据,只是重置指针:
position = 0limit = capacity
如果还有部分数据没读完,但希望保留未读数据并继续写入,可以使用 compact():
buffer.compact();
compact() 会把未读数据移动到缓冲区开头,然后切换到写模式。
# 5. 典型读文件流程
try (FileChannel channel = FileChannel.open(Path.of("data.txt"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
byte b = buffer.get();
// process byte
}
buffer.clear();
}
}
这个流程是 NIO Buffer 的基本套路:
- Channel 读取数据到 Buffer。
flip()切换为读模式。- 程序读取 Buffer。
clear()或compact()准备下一轮写入。
# 6. 堆内内存和直接内存
ByteBuffer 有两种常见分配方式:
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
allocate 创建堆内缓冲区,内存由 JVM 堆管理。
allocateDirect 创建直接缓冲区,内存在堆外,和操作系统 IO 交互时可能减少一次复制。
直接缓冲区适合:
- 大文件读写。
- 高频网络 IO。
- 生命周期较长、复用率高的缓冲区。
不适合:
- 大量短生命周期小 Buffer。
- 没有明显 IO 性能瓶颈的普通代码。
# 7. 常见错误
- 写完后忘记
flip(),导致读不到数据。 - 读完后忘记
clear(),导致后续无法继续写入。 - 不理解
clear()不会清除真实数据。 - 多线程共享同一个 Buffer,没有同步控制。
- 盲目使用直接内存,导致堆外内存压力和排查困难。
# 专家实践清单
| 操作 | 作用 | 常见错误 |
|---|---|---|
put | 写入 Buffer | 写满后未检查剩余空间 |
flip | 写模式切到读模式 | 忘记调用导致读不到数据 |
get | 从 Buffer 读取 | 读取超过 limit |
clear | 切回写模式 | 误以为清除了真实数据 |
compact | 保留未读数据再写 | 粘包半包处理不当 |
Buffer 状态机:
写模式:position 前进,limit=capacity
│ flip
▼
读模式:position=0,limit=原写入位置
│ clear / compact
▼
写模式:继续接收数据
# Tips 快问快答
Q:Buffer 的三个核心指针是什么?
A:capacity、position、limit。
Q:flip 做了什么?
A:把 limit 设为当前 position,再把 position 置 0,准备读取。
Q:clear 会清空底层数组吗?
A:不会,只重置 position 和 limit。
Q:compact 适合什么场景?
A:读了一部分后保留未读数据,再继续写入新数据。
Q:直接缓冲区和堆缓冲区区别是什么? A:直接缓冲区在堆外,适合底层 IO,但分配和回收成本更高。
Q:Buffer 线程安全吗? A:不是,多线程共享需要额外同步。
Q:为什么网络编程容易用到 compact?
A:TCP 可能半包,未解析完的数据需要保留到下一次读取。
Q:Buffer 最常见的新手错误是什么?
A:忘记在写入后调用 flip 再读取。
# 8. 总结
Buffer 是 NIO 的数据容器。掌握 Buffer 的关键是理解 capacity、position、limit,以及 flip()、clear()、compact() 这三个方法。只要这套状态机清楚了,NIO 文件和网络读写就会顺很多。