ByteBuf 详解(二)

本文详细介绍了Netty框架中ByteBuf的核心概念,包括可读字节数、可写字节数、索引管理、查找操作、派生缓冲区等特性。探讨了ByteBuf的读写操作、索引标记与重置、查找特定值的方法,以及如何创建派生缓冲区和进行数据复制。此外,还讲解了ByteBuf的分配策略,包括按需分配和池化技术,以及如何使用Unpooled工具类创建非池化的ByteBuf实例。

4、可读字节数

ByteBuf的可读字节数存储了实际数据。新分配的、包装的或者负责的缓冲区的默认的readerIndex值为0.任何名称以read或者skip开头的操作都将会检索或跳过位于当前readIndex的数据,并且将它增加已读字节数。

如果被调用的方法需要一个ByteBuf参数作为写入的目标,并且没有指定目标索引参数,那么该目标缓冲区的writerIndex也将被增加,例如:

readBytes(ByteBuf dest);

如果尝试在缓冲区的可读字节数已经耗尽时从中读取数据,那么将会引发一个IndexOutOf-BoundsException

下面的示例代码展示了如何读取所有可以读的字节。

  /**
     * 测试读取所有的可读字节
     */
    @Test
    public void readAbleByte() {
        ByteBuf byteBuf = Unpooled.buffer(16);
        byteBuf.writeBytes("abcdefg".getBytes());
        while (byteBuf.isReadable()) {
            System.out.print( (char)byteBuf.readByte());
        }


    }

5、可写字节数

可写字节分段是指一个拥有未定义内容的、写入就绪的内存区域。新分配的缓冲区的writerIndex的默认值为0。任何名称以write开头的操作都将从当前的writerIndex处开始写数据,并将它增加已经写入的字节数。如果写操作的目标也是ByteBuf,并且没有指定源索引的值,则源缓冲区的readerIndex也同样会被增加相同的大小。这个调用如下所示:

writeBytes(ByteBuf dest);

如果尝试往目标写入超过目标容量的数据,将会引发一个IndexOutOfBoundException[5]

以下示例是一个写入字节的示例:

    /**
     * 测试写入所有的数据
     */
    @Test
    public void writeByte(){
        ByteBuf buf = Unpooled.buffer(16);
        Random random = new Random();
        while(buf.writableBytes() >= 4){
            buf.writeInt(random.nextInt());
        }
    }

6、索引管理

JDK的inputStream定义了mark(int readlimit)和reset()方法,这些方法分别被用来将流中的当前位置标记为指定的值,以及将该流重置到该位置。

同样,可以通过调用markReadIndex()、markWriterIndex()、restWriterIndex()和resetReaderIndex()来标记和重置ByteBuf的ReaderIndex及WriterIndex。

可以通过调用readerIndex(int)、writerIndex(int)来将索引移动到指定位置。

还可以通过调用clear()方法来将readerIndex和writerIndex的值设置为0。注意,这并不会清除内存中的内容。

调用clear()方法比discardBytes()轻量得多,因为它将只是重置索引而不会复制任何得内存。

    /**
     * 测试ByteBuf得clear方法
     */
    @Test
    public void testClear(){
        ByteBuf byteBuf = Unpooled.buffer(16);
        byteBuf.writeBytes("abcdefg".getBytes());

        //从符合缓冲区中读取数据
        byte[] bytes = new byte[byteBuf.readableBytes()-1];
        byteBuf.readBytes(bytes);
        System.out.println(new String(bytes));

        System.out.println("调用clear方法前,readerIndex:" + byteBuf.readerIndex() + ",writerIndex:" + byteBuf.writerIndex()  );
        byteBuf.clear();

        System.out.println("调用clear方法后,readerIndex:" + byteBuf.readerIndex() + ",writerIndex:" + byteBuf.writerIndex()  );

    }

运行上面得示例,输出如下:

调用clear方法前,readerIndex:6,writerIndex:7
调用clear方法后,readerIndex:0,writerIndex:0

7、查找操作

ByteBuf中有多种可以用来确定指定值的索引的方法。最简单的是使用indexOf()方法。较复杂的查找可以通过那些需要一个ByteBufProcessor[6]作为参数的方法达成。这个接口只定义了一个方法:

boolean process(byte value)

它将检查输入值是否是正在查找的值。

ByteBufProcessor针对一些常见的值定义了许多便利的方法。假设你的应用程序需要和所谓的包含有以 NULL 结尾的内容的 Flash 套接字[7]集成。调用

forEachByte(ByteBufProcessor.FIND_NUL)

将简单高效地消费该 Flash 数据,因为在处理期间只会执行较少的边界检查。

下面的示例展示了一个查找回车符(\r)的例子。

/**
 * 测试查找操作
 */
@Test
public void testProcess(){
    ByteBuf byteBuf = Unpooled.buffer(16);
    byteBuf.writeBytes("ab\rcdefg\r\r".getBytes());
    int index = byteBuf.forEachByte(ByteProcessor.FIND_CR);

    System.out.println(index);
}

8、派生缓冲区

派生缓冲区为ByteBuf提供了以专门的方式来呈现其内容的视图。这类视图是通过以下方法被创建的:

  • duplicate();
  • slice();
  • slice(int, int);
  • Unpooled.unmodifiableBuffer(…);
  • order(ByteOrder);
  • readSlice(int)。

每个这些方法都将返回一个新的ByteBuf实例,它具有自己的读索引、写索引和标记索引。其内部存储和 JDK 的ByteBuffer一样也是共享的。这使得派生缓冲区的创建成本是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所以要小心。

ByteBuf复制

如果需要一个现有缓冲区的真实副本,请使用copy()或者copy(int, int)方法。不同于派生缓冲区,由这个调用所返回的ByteBuf拥有独立的数据副本。

    /**
     * 测试数据的分片
     */
    @Test
    public void testSlice(){
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);  //  创建一个用于保存给定字符串的字节的 ByteBuf
        ByteBuf sliced = buf.slice(0, 15); // 创建该 ByteBuf 从索引0 开始到索引15结束的一个新切片
        System.out.println(sliced.toString(utf8));  // 将打印“Netty in Action”
        buf.setByte(0, (byte)'J');   // 更新索引0 处的字节
        assert buf.getByte(0) == sliced.getByte(0);//  将会成功,因为数据是共享的,对其中一个所做的更改对另外一个也是可见的
    }

9、ByteBuf的其他可用方法

下表是一些有用的操作

名  称描  述
isReadable()如果至少有一个字节可供读取,则返回true
isWritable()如果至少有一个字节可被写入,则返回true
readableBytes()返回可被读取的字节数
writableBytes()返回可被写入的字节数
capacity()返回ByteBuf可容纳的字节数。在此之后,它会尝试再次扩展直 到达到maxCapacity()
maxCapacity()返回ByteBuf可以容纳的最大字节数
hasArray()如果ByteBuf由一个字节数组支撑,则返回true
array()如果 ByteBuf由一个字节数组支撑则返回该数组;否则,它将抛出一个UnsupportedOperationException异常

五、ByteBufHolder接口

我们经常发现,除了实际的数据负载之外,还需要存储各种属性。

在这里插入图片描述

ByteBufHolder的操作

名  称描  述
content()返回由这个ByteBufHolder所持有的ByteBuf
copy()返回这个ByteBufHolder的一个深拷贝,包括一个其所包含的ByteBuf的非共享副本
duplicate()返回这个ByteBufHolder的一个浅拷贝,包括一个其所包含的ByteBuf的共享副本

如果想要实现一个将其有效负载存储在ByteBuf中的消息对象,那么ByteBufHolder将是个不错的选择。

六、ByteBuf分配

在这一节中,我们将描述管理ByteBuf实例的不同方式。

1、按需分配:ByteBufAllocator

为了降低分配和释放内存的开销,Netty 通过interface ByteBufAllocator实现了(ByteBuf的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf实例。

在这里插入图片描述

通常我们可以通过Channel(每个都可以有一个同步的ByteBufAllocator实例)或者绑定到ChannelHandler的ChannelHandlerContext获取一个到ByteBufAllocator的引用:

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();

        ByteBufAllocator alloc = ctx.alloc();

        ByteBufAllocator alloc1 = channel.alloc();
        
        
        ctx.flush();
    }

netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。

虽然 Netty 默认使用了PooledByteBufAllocator,但这可以很容易地通过Channel-Config API 或者在引导你的应用程序时指定一个不同的分配器来更改。

    @Test
    public void testUnpooledByteBufAllocator(){
        UnpooledByteBufAllocator  allocator = new UnpooledByteBufAllocator(true);
        ByteBuf buffer = allocator.buffer();

        buffer.writeBytes("hello netty".getBytes());
        byte[] bytes = new byte[buffer.readableBytes()];

        buffer.readBytes(bytes);
        System.out.println(new String(bytes));

    }

1、Unpooled 缓冲区

可能某些情况下,你未能获取一个到ByteBufAllocator的引用。对于这种情况,Netty 提供了一个简单的称为Unpooled的工具类,它提供了静态的辅助方法来创建未池化的ByteBuf实例

名  称描  述
buffer() buffer(int initialCapacity) buffer(int initialCapacity, int maxCapacity)返回一个未池化的基于堆内存存储的ByteBuf
directBuffer() directBuffer(int initialCapacity) directBuffer(int initialCapacity, int maxCapacity)返回一个未池化的基于直接内存存储的ByteBuf
wrappedBuffer()返回一个包装了给定数据的ByteBuf
copiedBuffer()返回一个复制了给定数据的ByteBuf

Unpooled类还使得ByteBuf同样可用于那些并不需要 Netty 的其他组件的非网络项目,使得其能得益于高性能的可扩展的缓冲区 API。

ByteBufNetty 框架中的一个重要概念,它是一个字节容器,可以方便地进行字节的读写操作。下面就是 ByteBuf 的用法详解。 1. 创建 ByteBuf 创建 ByteBuf 的方式有两种,一种是使用 Unpooled 工具类创建,另一种是使用 ByteBufAllocator 工具类创建。 使用 Unpooled 工具类创建: ```java // 创建一个容量为10个字节的 ByteBuf ByteBuf buf = Unpooled.buffer(10); ``` 使用 ByteBufAllocator 工具类创建: ```java // 获取 ByteBufAllocator 实例 ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; // 创建一个容量为10个字节的 ByteBuf ByteBuf buf = allocator.buffer(10); ``` 2. 写入数据 写入数据可以使用 ByteBuf 的 write 方法,它有很多重载方法,可以写入不同类型的数据。 ```java // 写入一个字节 byte b = 1; buf.writeByte(b); // 写入一个整数 int i = 100; buf.writeInt(i); // 写入一个字符串 String str = "hello world"; buf.writeBytes(str.getBytes()); ``` 3. 读取数据 读取数据可以使用 ByteBuf 的 read 方法,它也有很多重载方法,可以读取不同类型的数据。 ```java // 读取一个字节 byte b = buf.readByte(); // 读取一个整数 int i = buf.readInt(); // 读取一个字符串 byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); String str = new String(bytes); ``` 4. 获取 ByteBuf 的信息 可以使用 ByteBuf 的一些方法获取它的一些信息,比如容量、读索引、写索引等。 ```java // 获取容量 int capacity = buf.capacity(); // 获取可读字节数 int readableBytes = buf.readableBytes(); // 获取可写字节数 int writableBytes = buf.writableBytes(); // 获取读索引 int readerIndex = buf.readerIndex(); // 获取写索引 int writerIndex = buf.writerIndex(); ``` 5. 派生 ByteBuf 可以使用 ByteBuf 的 slice、duplicate、readSlice 等方法来派生一个新的 ByteBuf,这个新的 ByteBuf 与原来的 ByteBuf 共享底层的字节数组,但是它们的读写索引是独立的。 ```java // 派生一个新的 ByteBuf,共享底层的字节数组 ByteBuf slice = buf.slice(); // 派生一个新的 ByteBuf,共享底层的字节数组 ByteBuf duplicate = buf.duplicate(); // 派生一个新的 ByteBuf,共享底层的字节数组,但是它的读写索引是独立的 ByteBuf slice = buf.readSlice(5); ``` 6. 释放 ByteBuf 在使用完 ByteBuf 后,需要将它释放,否则会出现内存泄漏问题。可以使用 ByteBuf 的 release 方法来释放它。 ```java buf.release(); ``` 以上就是 ByteBuf 的用法详解,希望对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值