Netty - ByteBuf

suaxi
2026-07-08 / 0 评论 / 4 阅读 / 正在检测是否收录...
3.4 ByteBuf

(1)创建

// 创建一个池化基于直接内存的 ByteBuf(指定初始容量为16)
ByteBuf bf = ByteBufAllocator.DEFAULT.buffer(16);


(2)直接内存、堆内存

// 创建池化基于堆内存的 ByteBuf
ByteBuf bf = ByteBufAllocator.DEFAULT.heapBuffer(16);

// 创建池化基于直接内存的 ByteBuf
ByteBuf bf = ByteBufAllocator.DEFAULT.directBuffer(16);

堆内存:创建和销毁的成本很高,但读写性能好(少一次内存复制)

直接内存:不在 jvm 管辖范围内,GC 压力小


(3)池化、非池化

池化:可以重用 ByteBuf,高并发场景下更节约内存,能较少内存溢出的可能

非池化:每次使用时都得创建新的 ByteBuf 实例,且在堆内存、直接内存不同的场景下影响不同

Netty 4.1 后非 Android 平台默认开启池化


(4)组成

3.4.4ByteBuf组成.png


(5)写入

方法参数说明备注
writeBoolean(boolean value)写入 boolean 值一字节 01 表示 true、00 表示 false
writeByte(int value)写入 byte 值
writeShort(int value)写入 short 值
writeInt(int value)写入 int 值Big Endian,即 0x250,写入后 00 00 02 50(网络编程一般情况下默认使用大端写入)
writeIntLE(int value)写入 int 值Little Endian,即 0x250,写入后 50 02 00 00
writeLong(long value)写入 long 值
writeChar(int value)写入 char 值
writeFloat(float value)写入 float 值
writeDouble(double value)写入 double 值
writeBytes(ByteBuf src)写入 netty 的 ByteBuf
writeBytes(byte[] src)写入 byte[]
writeBytes(ByteBuffer src)写入 nio 的 ByteBuffer
int writeCharSequence(CharSequence sequence, Charset charset)写入字符串


演示:

package com.sw.netty._04;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;

public class ByteBufTest {
    public static void main(String[] args) {
        ByteBuf bf = ByteBufAllocator.DEFAULT.buffer(10);
        bf.writeBytes(new byte[]{1, 2, 3, 4});
        log(bf);
        // read index:0 write index:4 capacity:10
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04                                     |....            |
        // +--------+-------------------------------------------------+----------------+

        // 写入整型 10(4字节)
        bf.writeInt(10);
        log(bf);
        // read index:0 write index:8 capacity:10
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04 00 00 00 0a                         |........        |
        // +--------+-------------------------------------------------+----------------+
    }

    private static void log(ByteBuf buffer) {
        int length = buffer.readableBytes();
        int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder buf = new StringBuilder(rows * 80 * 2)
                .append("read index:").append(buffer.readerIndex())
                .append(" write index:").append(buffer.writerIndex())
                .append(" capacity:").append(buffer.capacity())
                .append(NEWLINE);
        appendPrettyHexDump(buf, buffer);
        System.out.println(buf);
    }
}


(6)扩容

写入过程中,当 ByteBuf 容量不够时会进行扩容:

  • 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍进行扩容,如:写入后大小为 12 ,则扩容后 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个 2^n 进行扩容,如:写入后大小为 513,则扩容后 capacity 是 2^10=1024(2^9=512 已经不够了)
  • 扩容不能超过 max capacity 最大容量
package com.sw.netty._04;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;

public class ByteBufTest {
    public static void main(String[] args) {
        ByteBuf bf = ByteBufAllocator.DEFAULT.buffer(16);
        bf.writeBytes(new byte[]{1, 2, 3, 4});
        log(bf);
        // read index:0 write index:4 capacity:10
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04                                     |....            |
        // +--------+-------------------------------------------------+----------------+

        // 写入整型 10(4字节)
        bf.writeInt(10);
        log(bf);
        // read index:0 write index:8 capacity:10
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04 00 00 00 0a                         |........        |
        // +--------+-------------------------------------------------+----------------+

        // 继续写入(触发扩容)
        bf.writeBytes(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
        log(bf);
        // read index:0 write index:17 capacity:64
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04 00 00 00 0a 01 02 03 04 05 06 07 08 |................|
        // |00000010| 09                                              |.               |
        // +--------+-------------------------------------------------+----------------+
    }

    private static void log(ByteBuf buffer) {
        int length = buffer.readableBytes();
        int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder buf = new StringBuilder(rows * 80 * 2)
                .append("read index:").append(buffer.readerIndex())
                .append(" write index:").append(buffer.writerIndex())
                .append(" capacity:").append(buffer.capacity())
                .append(NEWLINE);
        appendPrettyHexDump(buf, buffer);
        System.out.println(buf);
    }
}


(7)读取

// 读取一个字节
System.out.println(buffer.readByte());

// 重复读取
// 1. 做标记
buffer.markReaderIndex();
System.out.println(buffer.readInt());

// 2. 重置到标记位置
buffer.resetReaderIndex();
System.out.println(buffer.readInt());

注:io 读取操作类似于水流出水管,ByteBuf 提供的 getXxx() 方法也可以重复读取,且不会改变 read index


(8)retain、release

Netty 中使用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口

  • 每个 ByteBuf 对象的初始计数为 1
  • release 计数器减一
  • retain 计数器加一,调用者调用了 retain 方法时,在未使用完之前,即使其他 handler 调用了 release 方法。内存也不会释放,因为引用计数器此时不为 0
  • 当引用计数器为 0 时,底层内存会被回收(即使 ByteBuf 对象还存在,释放之后便不能再正常使用)


内存释放规则:谁最后一个使用,谁负责释放

  • 入站处理规则:

    • 原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后续的 handler 传递,此时无须 release
    • ByteBuf 被转换为其他 Java 对象时,ByteBuf 对象必须 release 处理(在调用链中传递的是转换后的对象,或者说在这个 handler 中转换了,也使用完了,无需再向后传递)
    • 如果不需要再向后传递,必须 release
    • 传递失败时,需要在 flnally 中 release
    • 如果 msg 一直传递到了 tail,则由 tail 负责 release
  • 出站处理规则:

    • 出站 msg 最终会以 ByteBuf 的形式输出,会一直传递到 head,由 head 负责 release
  • 异常处理规则:

    • ByteBuf 在 pipeline 中出现异常时,必须 release,即申请的资源需要关闭/归还/释放


(8)slice 切片

对原始 ByteBuf 进行切片操作,可以生成多个 ByteBuf,切片时不会发生内存复制,还是使用原 ByteBuf 的内存(零拷贝),但生成的 ByteBuf 各自独立维护读指针、写指针

3.4.8slice切片.png

注:

  • 切片后的 ByteBuf 的 max capacity 固定为切片区间(read index, write index)的大小,且不能追加写入新的内容
  • 切片后的 ByteBuf 如果内容发生了变化,原始 ByteBuf 的内容也会随之变化(底层使用了同一块内存)
package com.sw.netty._04;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static utils.ByteBufferUtil.log;

public class ByteBufSliceTest {
    public static void main(String[] args) {
        ByteBuf bf = ByteBufAllocator.DEFAULT.buffer(16);
        bf.writeBytes(new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'd', 'c', 'd', 'e', 'f'});
        log(bf);
        // read index:0 write index:17 capacity:64
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 30 31 32 33 34 35 36 37 38 39 61 62 64 63 64 65 |0123456789abdcde|
        // |00000010| 66                                              |f               |
        // +--------+-------------------------------------------------+----------------+

        // 切片(切片过程中不会发生数据的复制)
        ByteBuf bf1 = bf.slice(0, 5);
        log(bf1);
        // read index:0 write index:5 capacity:5
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 30 31 32 33 34                                  |01234           |
        // +--------+-------------------------------------------------+----------------+
        bf1.retain();

        ByteBuf bf2 = bf.slice(5, 5);
        log(bf2);
        // read index:0 write index:5 capacity:5
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 35 36 37 38 39                                  |56789           |
        // +--------+-------------------------------------------------+----------------+

        // 释放内存
        bf.release();

        System.out.println("ByteBuf 内存已释放");

        // 如果后续还要使用,需要显式 retain,防止计数器为0,内存被释放
        log(bf);
    }
}


(10)duplicate

3.4.10duplicate截取.png

截取原始 ByteBuf 的所有内容(零拷贝),没有 max capacity 限制,且独立维护读写指针


(11)CompositeByteBuf

将多个 ByteBuf 合并为一个逻辑上的 ByteBuf(零拷贝),该方法在内部维护了一个 Component 数组,每个 Component 维护一个 ByteBuf,代表整体中的某一段数据

package com.sw.netty._04;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;

import static utils.ByteBufferUtil.log;

public class ByteBufCompositeTest {
    public static void main(String[] args) {
        ByteBuf bf = ByteBufAllocator.DEFAULT.buffer();
        bf.writeBytes(new byte[]{'0', '1', '2', '3', '4', '5'});

        ByteBuf bf1 = ByteBufAllocator.DEFAULT.buffer();
        bf1.writeBytes(new byte[]{'6', '7', '8', '9', 'a'});

        // 合并 ByteBuf
        // 方式一:(业务场景复杂,数据量大时,会产生很多io操作,不推荐)
        // ByteBuf bf2 = ByteBufAllocator.DEFAULT.buffer();
        // bf2.writeBytes(bf).writeBytes(bf1);
        // log(bf2);

        // 方式二:(可以避免频繁的数据拷贝操作)
        CompositeByteBuf bf2 = ByteBufAllocator.DEFAULT.compositeBuffer();
        // 通过 increaseWriteIndex 参数调整写入指针,不然合并不进去
        bf2.addComponents(true, bf, bf1);
        log(bf2);
        // read index:0 write index:11 capacity:11
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 30 31 32 33 34 35 36 37 38 39 61                |0123456789a     |
        // +--------+-------------------------------------------------+----------------+
    }
}


(13)Unpooled

Unpooled 是一个工具类,提供非池化 ByteBuf 的创建、组合、复制(零拷贝)等操作

package com.sw.netty._04;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;

import static utils.ByteBufferUtil.log;

public class ByteBufUnpooledTest {
    public static void main(String[] args) {
        ByteBuf bf = ByteBufAllocator.DEFAULT.buffer(4);
        bf.writeBytes(new byte[]{1, 2, 3, 4});
        ByteBuf bf1 = ByteBufAllocator.DEFAULT.buffer(4);
        bf.writeBytes(new byte[]{5, 6, 7, 8});

        // 当包装多个 ByteBuf 时,底层使用 CompositeByteBuf
        ByteBuf bf2 = Unpooled.wrappedBuffer(bf, bf1);
        System.out.println(bf2.getClass()); // class io.netty.buffer.CompositeByteBuf
        log(bf2);
        // read index:0 write index:8 capacity:8
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 01 02 03 04 05 06 07 08                         |........        |
        // +--------+-------------------------------------------------+----------------+

        // 普通 byte 数组同理
        ByteBuf bf3 = Unpooled.wrappedBuffer(new byte[]{'a', 'b'}, new byte[]{'c', 'd'});
        System.out.println(bf3.getClass()); // class io.netty.buffer.CompositeByteBuf
        log(bf3);
        // read index:0 write index:4 capacity:4
        //         +-------------------------------------------------+
        //         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
        // +--------+-------------------------------------------------+----------------+
        // |00000000| 61 62 63 64                                     |abcd            |
        // +--------+-------------------------------------------------+----------------+
    }
}


(14)ByteBuf 优势

  • 池化可重用,节约内存,在一定程度上减少内存溢出的可能
  • 读写指针分离,不需要像 ByteBuffer 一样显式切换读写模式
  • 自动扩容
  • 支持链式调用
  • slice、duplicate、CompositeByteBuf 等使用零拷贝,减少内存的复制
0

评论 (0)

取消