Java NIO ByteBuffer 核心详解(简化版)
为学习 Netty ByteBuf 打基础
一、为什么需要 ByteBuffer?
网络编程中,数据以字节为单位传输。如果每次都直接操作网络 IO,性能会很低。
解决方案:使用缓冲区(Buffer)
- 先把数据放入内存缓冲区
- 批量读写,减少 IO 次数
- 大幅提升性能
问题:Java NIO 的 ByteBuffer 使用复杂,所以 Netty 自己实现了 ByteBuf。
二、ByteBuffer 核心概念
ByteBuffer 就像一个可读可写的数组,但需要手动管理读写位置。
2.1 四个核心属性
0 <= mark <= position <= limit <= capacity
| 属性 | 作用 |
|---|---|
| capacity | 缓冲区总容量(固定不变) |
| position | 当前读/写位置(会移动) |
| limit | 可读/可写的边界 |
| mark | 标记位置(用于回退,可选) |
三、两种模式对比
3.1 写入模式(初始状态)
创建: ByteBuffer buffer = ByteBuffer.allocate(6);
[_][_][_][_][_][_]
↑ ↑
position=0 limit=capacity=6
写入 3 个字节后:
[A][B][C][_][_][_]
↑ ↑
position=3 limit=6
写入模式特点:
- position:下一个要写入的位置
- limit:最多能写到哪里(= capacity)
- capacity:总容量
3.2 读取模式(调用 flip() 后)
buffer.flip(); // 切换到读取模式
[A][B][C][_][_][_]
↑ ↑ ↑
position=0 limit=3 capacity=6
读取模式特点:
- position:下一个要读取的位置(重置为 0)
- limit:可读数据的边界(= 之前的 position)
- capacity:总容量不变
四、核心方法详解
4.1 flip() - 写转读
public final Buffer flip() {
limit = position; // 限制读取范围
position = 0; // 从头开始读
mark = -1;
return this;
}
使用场景:写完数据后,准备读取
4.2 clear() - 清空重用
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
注意:
- 数据并未真正清除
- 只是重置指针,准备重新写入
- 未读数据会被覆盖
4.3 compact() - 压缩未读数据
// 将未读数据移到开头,继续写入
public ByteBuffer compact() {
int remaining = limit - position; // 未读数据量
// 将未读数据复制到开头
System.arraycopy(array, position, array, 0, remaining);
position = remaining;
limit = capacity;
return this;
}
使用场景:
- 还有未读数据
- 但需要继续写入新数据
- 不想丢弃未读数据
五、实战示例
5.1 完整读写流程
public class ByteBufferSimpleDemo {
public static void main(String[] args) {
// 1. 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("初始: " + buffer);
// 输出: pos=0 lim=10 cap=10
// 2. 写入数据
buffer.put("ABCD".getBytes());
System.out.println("写入后: " + buffer);
// 输出: pos=4 lim=10 cap=10
// 3. 切换到读模式
buffer.flip();
System.out.println("flip后: " + buffer);
// 输出: pos=0 lim=4 cap=10
// 4. 读取数据
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("读取: " + new String(data));
// 输出: ABCD
System.out.println("读取后: " + buffer);
// 输出: pos=4 lim=4 cap=10
// 5. 清空重用
buffer.clear();
System.out.println("clear后: " + buffer);
// 输出: pos=0 lim=10 cap=10
}
}
5.2 compact() 使用场景
public class CompactDemo {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
// 写入 5 个字节
buffer.put("ABCDE".getBytes());
System.out.println("写入: " + buffer);
// pos=5 lim=10 cap=10
// 切换到读模式
buffer.flip();
System.out.println("flip: " + buffer);
// pos=0 lim=5 cap=10
// 只读取 2 个字节
byte[] temp = new byte[2];
buffer.get(temp);
System.out.println("读取2字节: " + new String(temp));
// 输出: AB
System.out.println("读取后: " + buffer);
// pos=2 lim=5 cap=10 (还有CDE未读)
// 使用 compact 保留未读数据
buffer.compact();
System.out.println("compact: " + buffer);
// pos=3 lim=10 cap=10
// 内部: [C][D][E][_][_][_][_][_][_][_]
// 继续写入新数据
buffer.put("FG".getBytes());
System.out.println("继续写: " + buffer);
// pos=5 lim=10 cap=10
// 内部: [C][D][E][F][G][_][_][_][_][_]
}
}
六、常见使用模式
6.1 NIO 服务器读写示例
// 读取客户端数据
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从 channel 读取数据到 buffer(写入模式)
int bytesRead = client.read(buffer);
if (bytesRead > 0) {
// 切换到读模式
buffer.flip();
// 处理数据
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("收到: " + new String(data));
// 准备回写
buffer.clear();
buffer.put("响应数据".getBytes());
buffer.flip();
// 写回客户端
client.write(buffer);
}
}
七、核心要点总结
7.1 记住这个流程
1. allocate() → 创建缓冲区(写模式)
2. put() → 写入数据
3. flip() → 切换到读模式
4. get() → 读取数据
5. clear() → 清空,准备重新写入
或 compact() → 保留未读数据,继续写入
7.2 模式切换对比表
| 操作 | position | limit | 说明 |
|---|---|---|---|
| 初始/clear() | 0 | capacity | 写模式 |
| 写入数据 | 递增 | capacity | 写模式 |
| flip() | 0 | 旧position | 读模式 |
| 读取数据 | 递增 | 不变 | 读模式 |
| compact() | 未读数据量 | capacity | 写模式 |
7.3 方法选择
- clear():读完所有数据,准备重新写入
- compact():还有未读数据,但要继续写入
- flip():写完数据,准备读取
八、ByteBuffer 的问题
虽然 ByteBuffer 功能完整,但存在以下问题:
- 读写模式需要手动切换(flip/clear/compact)
- 容量固定,无法动态扩展
- API 不够友好,容易出错
- 只有一个 position,不能同时读写
这就是 Netty 为什么要实现 ByteBuf 的原因!
ByteBuf 的优势:
- 读写索引分离(readerIndex / writerIndex)
- 自动扩容
- 零拷贝支持
- 引用计数管理内存
- 链式调用,API 更友好
下一步:学习 Netty ByteBuf 的实现原理和使用方式
575



被折叠的 条评论
为什么被折叠?



