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。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值