略解ByteBuf

Posted scy251147

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了略解ByteBuf相关的知识,希望对你有一定的参考价值。

说到ByteBuf,我们并不陌生,官网给的解释为,一个可以进行随机访问或者是顺序访问的字节集合,它是NIO buffers缓冲的底层抽象。既然是底层抽象,那么我们就可以基于其衍生出很多的具体实现出来,事实上,netty中的很多缓冲组件都是基于此抽象类做的扩展。

随机访问索引

和普通的字节数据一样,ByteBuf也是从0开始索引的。这就意味着第一个字节的索引永远是0,而最后一个字节的索引则是capacity。举个例子,当我们去遍历缓冲中的所有字节的时候,我们可以按照如下方式来做:

 ByteBuf buffer = ...;
 for (int i = 0; i < buffer.capacity(); i ++) {
     byte b = buffer.getByte(i);
     System.out.println((char) b);
 }

可以清楚的看到,其使用方式和遍历字节数组一样的做法。我们可以随机的获取缓冲区里面的任意一个字节。

顺序访问索引

这个ByteBuf的实现中,有三个比较有意思的属性,readerIndex,writerIndex,capacity,从字面意思上,我们可以理解为读索引,写索引,容量。下图则展示了三个属性之间的关系:

技术图片

首先是readable bytes(可读字节数组),里面放置的是真实的数据,当使用带有read或者是skip的方法来操作此数据内容的时候,都将导致readerIndex递增。如果当前内容读取完毕,没有更多的内容可以读取,那么尝试读取将会抛出IndexOutOfBoundsException。默认情况下,一个新分配的缓冲区或者包装的缓冲区或者复制的缓冲区,其readerIndex的初始值为0。示例读取代码如下:

// Iterates the readable bytes of a buffer.
 ByteBuf buffer = ...;
 while (buffer.isReadable()) {
     System.out.println(buffer.readByte());
 }

其次是writable bytes(可写字节数组),里面是空数据,待被真实数据覆盖。当使用带有write的方法来操作此数据内容的时候,都将导致writerIndex递增。如果当前已无足够的空间可写,那么尝试写入将会抛出IndexOutOfBoundsException。默认情况下,一个新分配的缓存区,其writerIndex的初始值为0。包装的缓冲区或者复制的缓冲区,其writerIndex等于capacity。示例写入代码如下:

 // Fills the writable bytes of a buffer with random integers.
 ByteBuf buffer = ...;
 while (buffer.maxWritableBytes() >= 4) {
     buffer.writeInt(random.nextInt());
 }
 

最后是discardable bytes(废弃字节数组),此数组里面是已经读取过的数据。开始的时候,其值默认为0,但是当进行读取操作的时候,它的值开始慢慢递增,直至和writerIndex相等。这些字节可以通过调用discardReadBytes()方法来进行释放,释放前和释放后的图示示例如下:

释放前:

技术图片

释放后:

技术图片

需要注意的是,不同缓冲区的底层实现,可能会让writable bytes里面填充进完全不同的数据,所以使用此方法的时候,还请审慎。

你可以调用clear()方法来重置readerIndex和writerIndex为0。此方法不会清理掉真实的数据,而是仅仅重置这两个索引。

清理前:

技术图片

清理后:

技术图片

需要注意的是,此种情况下可能会覆盖原有缓冲数据,使用的事情请谨慎。

搜索操作

简单的单个字节搜索,可以使用indexOf(int, int, byte)bytesBefore(int, int, byte)来实现。bytesBefore(byte) 适用于简单的String搜索。  forEachByte(int, int, ByteBufProcessor) 适用于比较复杂的搜索。

创建缓冲副本

你可以使用duplicate(), slice() 或者 slice(int, int)来创建已有缓冲的衍生副本。衍生的缓冲副本将会有独立的readerIndex,writerIndex和标记索引,但是如同其他NIO 缓冲区一样,他会共享内部的数据。为了能够获得一份真正的全新的缓冲拷贝,可以使用copy()方法来进行。需要注意的是,衍生的缓冲将不会调用retain()方法,因为其reference count将不会增加。

JDK类型转换

你可以使用array()方法来使ByteBuf支持字节数组(比如 byte[])。同时也可以使用hasArray()方法来检测其是否支持字节数组。

你可以使用nioBuffer()方法来使ByteBuf支持NIO ByteBuffer。同时也可以使用nioBufferCount()方法来检测其是否能够被转换为NIO buffer。

你可以使用toString(Charset)方法来使ByteBuf转换成String。需要注意的是,toString()方法并非类型转换的方法。

你可以使用ByteBufInputStream和ByteBufOutputStream来进行IO流的转换。

以上是关于略解ByteBuf的主要内容,如果未能解决你的问题,请参考以下文章

Netty入门——ByteBuf

iOS中的roundceilfloor函数略解

iOS中的round/ceil/floorf函数略解

Centos 5/6启动过程略解

树上差分略解

exkmp略解