欢迎光临
我们一直在努力

Netty-ByteBuf核心详解-简化版

Netty ByteBuf 核心详解(简化版)

ByteBuf 是 Netty 对 ByteBuffer 的优化实现

一、为什么需要 ByteBuf?

1.1 ByteBuffer 的痛点

// ByteBuffer 使用繁琐
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("data".getBytes());
buffer.flip(); // 必须手动切换模式!
byte[] data = new byte[buffer.limit()];
buffer.get(data);
buffer.clear(); // 必须手动清理!

问题:

  • 读写模式需要手动切换(flip/clear/compact)
  • 只有一个 position 指针,不能同时读写
  • 容量固定,无法动态扩展
  • API 不友好,容易出错

1.2 ByteBuf 的优势

// ByteBuf 使用简单
ByteBuf buffer = Unpooled.buffer(10);
buffer.writeBytes("data".getBytes()); // 直接写
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data); // 直接读,无需 flip!
buffer.clear(); // 清理

优势:

  • ✅ 读写索引分离(readerIndex / writerIndex)
  • ✅ 无需手动切换读写模式
  • ✅ 支持自动扩容
  • ✅ 零拷贝支持
  • ✅ 引用计数管理内存
  • ✅ 链式调用,API 友好

二、ByteBuf 核心原理

2.1 双指针设计

ByteBuf 使用两个独立的索引:

0 <= readerIndex <= writerIndex <= capacity

属性作用
readerIndex 读取位置指针
writerIndex 写入位置指针
capacity 缓冲区总容量

2.2 内存区域划分

+——————-+——————+——————+
| discardable bytes | readable bytes | writable bytes |
| (已读废弃区域) | (可读区域) | (可写区域) |
+——————-+——————+——————+
| | | |
0 <= readerIndex <= writerIndex <= capacity

三个区域:

  • discardable bytes:0 ~ readerIndex,已读数据,可丢弃
  • readable bytes:readerIndex ~ writerIndex,可读数据
  • writable bytes:writerIndex ~ capacity,可写空间

  • 三、ByteBuf 使用流程图解

    3.1 初始状态

    创建: ByteBuf buffer = Unpooled.buffer(10);

    [_][_][_][_][_][_][_][_][_][_]

    readerIndex=0
    writerIndex=0
    capacity=10

    3.2 写入数据

    buffer.writeBytes("ABCD".getBytes());

    [A][B][C][D][_][_][_][_][_][_]
    ↑ ↑
    readerIndex=0 writerIndex=4

    关键点:

    • writerIndex 自动移动到 4
    • readerIndex 保持不变
    • 无需调用 flip()

    3.3 读取数据

    byte b1 = buffer.readByte(); // 读取 'A'
    byte b2 = buffer.readByte(); // 读取 'B'

    [A][B][C][D][_][_][_][_][_][_]
    ↑ ↑
    readerIndex=2 writerIndex=4

    关键点:

    • readerIndex 自动移动到 2
    • writerIndex 保持不变
    • 可读数据:writerIndex – readerIndex = 2

    3.4 丢弃已读数据

    buffer.discardReadBytes(); // 类似 ByteBuffer.compact()

    [C][D][_][_][_][_][_][_][_][_]
    ↑ ↑
    readerIndex=0 writerIndex=2

    作用:

    • 将未读数据移到开头
    • 释放已读空间
    • 增加可写空间

    四、ByteBuf vs ByteBuffer 对比

    4.1 API 对比

    操作ByteBufferByteBuf
    创建 ByteBuffer.allocate(10) Unpooled.buffer(10)
    写入 put(data) writeBytes(data)
    切换模式 flip() ⚠️ 无需切换 ✅
    读取 get(data) readBytes(data)
    清空 clear() clear()
    压缩 compact() discardReadBytes()

    4.2 完整示例对比

    ByteBuffer 方式:

    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.put("ABCD".getBytes());
    buffer.flip(); // ⚠️ 必须切换模式

    byte[] data = new byte[buffer.limit()];
    buffer.get(data);
    System.out.println(new String(data)); // ABCD

    buffer.clear(); // ⚠️ 必须清理

    ByteBuf 方式:

    ByteBuf buffer = Unpooled.buffer(10);
    buffer.writeBytes("ABCD".getBytes());
    // ✅ 无需 flip()

    byte[] data = new byte[buffer.readableBytes()];
    buffer.readBytes(data);
    System.out.println(new String(data)); // ABCD

    buffer.clear();


    五、实战示例

    5.1 基础读写示例

    public class ByteBufBasicDemo {
    public static void main(String[] args) {
    // 1. 创建缓冲区
    ByteBuf buffer = Unpooled.buffer(10);
    System.out.println("初始状态:");
    printBuffer(buffer);
    // 输出: readerIndex=0, writerIndex=0, capacity=10

    // 2. 写入数据
    buffer.writeBytes("love".getBytes());
    System.out.println("\\n写入 'love' 后:");
    printBuffer(buffer);
    // 输出: readerIndex=0, writerIndex=4, capacity=10

    // 3. 读取数据(逐字节)
    System.out.println("\\n逐字节读取:");
    while (buffer.isReadable()) {
    byte b = buffer.readByte();
    System.out.print((char) b + " "); // l o v e
    }
    System.out.println("\\n\\n读取完成后:");
    printBuffer(buffer);
    // 输出: readerIndex=4, writerIndex=4, capacity=10

    // 4. 丢弃已读数据
    buffer.discardReadBytes();
    System.out.println("\\ndiscardReadBytes 后:");
    printBuffer(buffer);
    // 输出: readerIndex=0, writerIndex=0, capacity=10

    // 5. 清空缓冲区
    buffer.clear();
    System.out.println("\\nclear 后:");
    printBuffer(buffer);
    // 输出: readerIndex=0, writerIndex=0, capacity=10
    }

    private static void printBuffer(ByteBuf buffer) {
    System.out.println("readerIndex: " + buffer.readerIndex());
    System.out.println("writerIndex: " + buffer.writerIndex());
    System.out.println("capacity: " + buffer.capacity());
    System.out.println("readableBytes: " + buffer.readableBytes());
    System.out.println("writableBytes: " + buffer.writableBytes());
    }
    }

    5.2 常用方法示例

    public class ByteBufMethodsDemo {
    public static void main(String[] args) {
    ByteBuf buffer = Unpooled.buffer(10);

    // 写入方法
    buffer.writeByte(65); // 写入单字节 'A'
    buffer.writeInt(100); // 写入 int (4字节)
    buffer.writeBytes("Hi".getBytes()); // 写入字节数组

    // 查询方法
    int readable = buffer.readableBytes(); // 可读字节数
    int writable = buffer.writableBytes(); // 可写字节数
    boolean canRead = buffer.isReadable(); // 是否可读
    boolean canWrite = buffer.isWritable(); // 是否可写

    // 读取方法
    byte b = buffer.readByte(); // 读取单字节
    int i = buffer.readInt(); // 读取 int
    byte[] data = new byte[2];
    buffer.readBytes(data); // 读取到数组

    // 标记和重置
    buffer.markReaderIndex(); // 标记读位置
    buffer.readByte();
    buffer.resetReaderIndex(); // 重置到标记位置

    buffer.markWriterIndex(); // 标记写位置
    buffer.writeByte(66);
    buffer.resetWriterIndex(); // 重置到标记位置

    // 跳过字节
    buffer.skipBytes(2); // 跳过2个字节

    // 释放资源
    buffer.release(); // 引用计数-1
    }
    }


    六、ByteBuf 三种模式

    6.1 堆缓冲区(Heap Buffer)

    特点:数据存储在 JVM 堆内存中

    public class HeapBufferDemo {
    public static void main(String[] args) {
    // 创建堆缓冲区
    ByteBuf buffer = Unpooled.buffer(10);
    buffer.writeBytes("netty".getBytes());

    // 检查是否有支撑数组
    if (buffer.hasArray()) {
    byte[] array = buffer.array(); // 获取底层数组
    int offset = buffer.arrayOffset() + buffer.readerIndex();
    int length = buffer.readableBytes();

    System.out.println("底层数组: " + new String(array, offset, length));
    // 输出: netty
    }
    }
    }

    优点:

    • 快速分配和释放
    • 可直接访问底层数组

    缺点:

    • Socket IO 时需要复制到直接内存

    适用场景:后端业务逻辑、编解码

    6.2 直接缓冲区(Direct Buffer)

    特点:数据存储在堆外直接内存

    public class DirectBufferDemo {
    public static void main(String[] args) {
    // 创建直接缓冲区
    ByteBuf buffer = Unpooled.directBuffer(10);
    buffer.writeBytes("netty".getBytes());

    // 直接缓冲区没有支撑数组
    if (!buffer.hasArray()) {
    int length = buffer.readableBytes();
    byte[] array = new byte[length];
    buffer.getBytes(buffer.readerIndex(), array);

    System.out.println("数据: " + new String(array));
    // 输出: netty
    }
    }
    }

    优点:

    • Socket IO 性能高,无需复制
    • 避免 GC 压力

    缺点:

    • 分配和释放成本高
    • 不能直接访问底层数组

    适用场景:网络 IO、大量数据传输

    6.3 复合缓冲区(Composite Buffer)

    特点:多个 ByteBuf 的逻辑视图,零拷贝

    public class CompositeBufferDemo {
    public static void main(String[] args) {
    // 创建堆缓冲区
    ByteBuf heapBuf = Unpooled.buffer(3);
    heapBuf.writeBytes("net".getBytes());

    // 创建直接缓冲区
    ByteBuf directBuf = Unpooled.directBuffer(2);
    directBuf.writeBytes("ty".getBytes());

    // 创建复合缓冲区(零拷贝)
    CompositeByteBuf compositeBuf = Unpooled.compositeBuffer();
    compositeBuf.addComponents(true, heapBuf, directBuf);

    // 遍历所有组件
    for (ByteBuf buf : compositeBuf) {
    int length = buf.readableBytes();
    byte[] array = new byte[length];
    buf.getBytes(buf.readerIndex(), array);
    System.out.print(new String(array)); // netty
    }

    System.out.println("\\n总可读字节: " + compositeBuf.readableBytes());
    // 输出: 5
    }
    }

    优点:

    • 零拷贝组合多个缓冲区
    • 避免内存复制和分配

    缺点:

    • 不支持直接访问支撑数组
    • 需要先复制到堆内存

    适用场景:HTTP 消息聚合、协议组装


    七、ByteBuf 高级特性

    7.1 自动扩容

    ByteBuf buffer = Unpooled.buffer(4); // 初始容量 4
    buffer.writeBytes("Hello World".getBytes()); // 写入 11 字节
    // ✅ 自动扩容,无需手动处理
    System.out.println("capacity: " + buffer.capacity()); // > 11

    7.2 引用计数

    ByteBuf buffer = Unpooled.buffer(10);
    System.out.println("refCnt: " + buffer.refCnt()); // 1

    buffer.retain(); // 引用计数 +1
    System.out.println("refCnt: " + buffer.refCnt()); // 2

    buffer.release(); // 引用计数 -1
    System.out.println("refCnt: " + buffer.refCnt()); // 1

    buffer.release(); // 引用计数 -1,释放内存
    // buffer 已释放,不能再使用

    7.3 零拷贝

    // slice() – 切片,共享底层数据
    ByteBuf buffer = Unpooled.buffer(10);
    buffer.writeBytes("0123456789".getBytes());

    ByteBuf slice = buffer.slice(0, 5); // 切片 [0,5)
    System.out.println(slice.toString(CharsetUtil.UTF_8)); // 01234
    // ✅ 零拷贝,共享底层数据

    // duplicate() – 复制,共享底层数据
    ByteBuf duplicate = buffer.duplicate();
    // ✅ 零拷贝,独立的读写索引

    // copy() – 深拷贝,独立数据
    ByteBuf copy = buffer.copy();
    // ⚠️ 有拷贝,独立的数据和索引


    八、实战:Netty 中的使用

    8.1 ChannelHandler 中使用

    public class MyHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;

    try {
    // 读取数据
    int readableBytes = buf.readableBytes();
    byte[] data = new byte[readableBytes];
    buf.readBytes(data);

    System.out.println("收到: " + new String(data));

    // 写回响应
    ByteBuf response = Unpooled.copiedBuffer("OK", CharsetUtil.UTF_8);
    ctx.writeAndFlush(response);

    } finally {
    // ⚠️ 重要:释放资源
    buf.release();
    }
    }
    }

    8.2 编解码器中使用

    public class MyDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    // 检查是否有足够的数据
    if (in.readableBytes() < 4) {
    return; // 等待更多数据
    }

    // 标记读位置
    in.markReaderIndex();

    // 读取消息长度
    int length = in.readInt();

    // 检查是否有完整消息
    if (in.readableBytes() < length) {
    in.resetReaderIndex(); // 重置,等待完整消息
    return;
    }

    // 读取消息体
    byte[] body = new byte[length];
    in.readBytes(body);

    // 输出解码后的消息
    out.add(new String(body));
    }
    }


    九、核心要点总结

    9.1 ByteBuf vs ByteBuffer

    特性ByteBufferByteBuf
    读写指针 单指针 position 双指针 readerIndex/writerIndex
    模式切换 需要 flip() 无需切换
    容量 固定 可自动扩容
    内存管理 JVM GC 引用计数
    零拷贝 不支持 支持 slice/duplicate
    API 复杂 简洁友好

    9.2 使用建议

  • 选择合适的模式:

    • 网络 IO → 直接缓冲区
    • 业务逻辑 → 堆缓冲区
    • 消息组装 → 复合缓冲区
  • 注意内存释放:

    • 使用完毕后调用 release()
    • 或使用 try-finally 确保释放
  • 善用零拷贝:

    • 使用 slice() 切片
    • 使用 CompositeByteBuf 组合
  • 标记和重置:

    • 使用 mark/reset 实现回退
    • 避免重复读取

  • 十、常见问题

    Q1: 什么时候需要 release()?

    A: 当你是 ByteBuf 的最后一个使用者时。

    // 场景1: ChannelHandler 中
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    try {
    // 处理数据
    } finally {
    buf.release(); // ✅ 必须释放
    }
    }

    // 场景2: 传递给下一个 Handler
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.fireChannelRead(msg); // ✅ 无需释放,由下一个 Handler 负责
    }

    Q2: readBytes() 和 getBytes() 的区别?

    A:

    • readBytes(): 移动 readerIndex
    • getBytes(): 不移动 readerIndex

    ByteBuf buf = Unpooled.copiedBuffer("ABCD", CharsetUtil.UTF_8);

    byte[] data1 = new byte[2];
    buf.readBytes(data1); // 读取 AB,readerIndex 移动到 2

    byte[] data2 = new byte[2];
    buf.getBytes(0, data2); // 读取 AB,readerIndex 不变

    Q3: 如何避免内存泄漏?

    A:

  • 使用 try-finally 确保释放
  • 使用 Netty 的 ResourceLeakDetector 检测泄漏
  • 遵循"谁创建谁释放"原则
  • // 启用泄漏检测
    Dio.netty.leakDetection.level=PARANOID


    下一步:学习 Netty 的编解码器框架

    赞(0)
    未经允许不得转载:171主机测评 » Netty-ByteBuf核心详解-简化版
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址