简介
netty 中用于进行信息承载和交换的类叫做 ByteBuf,从名字能够看出这是 Byte 的缓存区,那么 ByteBuf 都有哪些个性呢?一起来看看。
ByteBuf 详解
netty 提供了一个 io.netty.buffer 的包,该包外面定义了各种类型的 ByteBuf 和其衍生的类型。
netty Buffer 的根底是 ByteBuf 类,这是一个抽象类,其余的 Buffer 类基本上都是由该类衍生而得的,这个类也定义了 netty 整体 Buffer 的基调。
先来看下 ByteBuf 的定义:
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
ByteBuf 实现了两个接口,别离是 ReferenceCounted 和 Comparable。Comparable 是 JDK 自带的接口,示意该类之间是能够进行比拟的。而 ReferenceCounted 示意的是对象的援用统计。当一个 ReferenceCounted 被实例化之后,其援用 count=1,每次调用 retain() 办法,就会减少 count,调用 release() 办法又会缩小 count。当 count 减为 0 之后,对象将会被开释,如果试图拜访被开释过后的对象,则会报拜访异样。
如果一个对象实现了 ReferenceCounted,并且这个对象外面蕴含的其余对象也实现了 ReferenceCounted,那么当容器对象的 count= 0 的时候,其外部的其余对象也会被调用 release() 办法进行开释。
综上,ByteBuf 是一个能够比拟的,能够计算援用次数的对象。他提供了序列或者随机的 byte 拜访机制。
留神的是,尽管 JDK 中有自带的 ByteBuffer 类,然而 netty 中的 ByteBuf 算是对 Byte Buffer 的从新实现。他们没有关联关系。
创立一个 Buff
ByteBuf 是一个抽象类,并不能间接用来实例化,尽管能够应用 ByteBuf 的子类进行实例化操作,然而 netty 并不举荐。netty 举荐应用 io.netty.buffer.Unpooled 来进行 Buff 的创立工作。Unpooled 是一个工具类,能够为 ByteBuf 调配空间、拷贝或者封装操作。
上面是创立几个不同 ByteBuf 的例子:
import static io.netty.buffer.Unpooled.*;
ByteBuf heapBuffer = buffer(128);
ByteBuf directBuffer = directBuffer(256);
ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
ByteBuf copiedBuffer = copiedBuffer(ByteBuffer.allocate(128));
下面咱们看到了 4 种不同的 buff 构建形式,一般的 buff、directBuffer、wrappedBuffer 和 copiedBuffer。
一般的 buff 是固定大小的堆 buff,而 directBuffer 是固定大小的 direct buff。direct buff 应用的是堆外内存,省去了数据到内核的拷贝,因而效率比一般的 buff 要高。
wrappedBuffer 是对现有的 byte arrays 或者 byte buffers 的封装,能够看做是一个视图,当底层的数据发生变化的时候,Wrapped buffer 中的数据也会发生变化。
Copied buffer 是对现有的 byte arrays、byte buffers 或者 string 的深拷贝,所以它和 wrappedBuffer 是不同的,Copied buffer 和原数据之间并不共享数据。
随机拜访 Buff
相熟汇合的敌人应该都晓得,要想随机拜访某个汇合,肯定是通过 index 来拜访的,ByteBuf 也一样,能够通过 capacity 或得其容量,而后通过 getByte 办法随机拜访其中的 byte,如下所示:
// 随机拜访
ByteBuf buffer = heapBuffer;
for (int i = 0; i < buffer.capacity(); i ++) {byte b = buffer.getByte(i);
System.out.println((char) b);
}
序列读写
读写要比拜访简单一点,ByteBuf 提供了两个 index 用来定位读和写的地位,别离是 readerIndex 和 writerIndex,两个 index 别离管制读和写的地位。
下图显示的一个 buffer 被分成了三局部,别离是可废除的 bytes、可读的 bytes 和可写的 bytes。
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
上图还表明了 readerIndex、writerIndex 和 capacity 的大小关系。
其中 readable bytes 是真正的内容,能够通过调用 read 或者 skip 的办法来进行拜访或者跳过,调用这些办法的时候,readerIndex 会同步减少,如果超出了 readable bytes 的范畴,则会抛出 IndexOutOfBoundsException。默认状况下 readerIndex=0。
上面是一个遍历 readable bytes 的例子:
// 遍历 readable bytes
while (directBuffer.isReadable()) {System.out.println(directBuffer.readByte());
}
首先通过判断是否是 readable 来决定是否调用 readByte 办法。
Writable bytes 是一个未确定的区域,期待被填充。能够通过调用 write* 办法对其操作,同时 writerIndex 会同步更新,同样的,如果空间不够的话,也会抛出 IndexOutOfBoundsException。默认状况下 新调配的 writerIndex =0,而 wrapped 或者 copied buffer 的 writerIndex=buf 的 capacity。
上面是一个应用 writable Byte 的例子:
// 写入 writable bytes
while (wrappedBuffer.maxWritableBytes() >= 4) {wrappedBuffer.writeInt(new Random().nextInt());
}
Discardable bytes 是曾经被读取过的 bytes,初始状况下它的值 =0,每当 readerIndex 右移的时候,Discardable bytes 的空间就会减少。如果想要齐全删除或重置 Discardable bytes,则能够调用 discardReadBytes() 办法,该办法会将 Discardable bytes 空间删除,将多余的空间放到 writable bytes 中,如下所示:
调用 discardReadBytes() 之前:+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
调用 discardReadBytes() 之后:+------------------+--------------------------------------+
| readable bytes | writable bytes (got more space) |
+------------------+--------------------------------------+
| | |
readerIndex (0) <= writerIndex (decreased) <= capacity
留神,尽管 writable bytes 变多了,然而其内容是不可控的,并不能保障外面的内容是空的或者不变。
调用 clear() 办法会将 readerIndex 和 writerIndex 清零,留神 clear 办法只会设置 readerIndex 和 writerIndex 的值,并不会清空 content,看上面的示意图:
调用 clear() 之前:+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
调用 clear() 之后:+---------------------------------------------------------+
| writable bytes (got more space) |
+---------------------------------------------------------+
| |
0 = readerIndex = writerIndex <= capacity
搜寻
ByteBuf 提供了单个 byte 的搜寻性能,如 indexOf(int, int, byte) 和 bytesBefore(int, int, byte) 两个办法。
如果是要对 ByteBuf 遍历进行搜寻解决的话,能够应用 forEachByte(int, int, ByteProcessor),这个办法接管一个 ByteProcessor 用于进行简单的解决。
其余衍生 buffer 办法
ByteBuf 还提供了很多办法用来创立衍生的 buffer,如下所示:
duplicate()
slice()
slice(int, int)
readSlice(int)
retainedDuplicate()
retainedSlice()
retainedSlice(int, int)
readRetainedSlice(int)
要留神的是,这些 buf 是建设在现有 buf 根底上的衍生品,他们的底层内容是一样的,只有 readerIndex, writerIndex 和做标记的 index 不一样。所以他们和原 buf 是有共享数据的。如果你心愿的是新建一个全新的 buffer,那么能够应用 copy() 办法或者后面提到的 Unpooled.copiedBuffer。
在后面大节中,咱们讲到 ByteBuf 是一个 ReferenceCounted, 这个特色在衍生 buf 中就用到了。咱们晓得调用 retain() 办法的时候,援用 count 会减少,然而对于 duplicate(), slice(), slice(int, int) 和 readSlice(int) 这些办法来说,尽管他们也是援用,然而没有调用 retain() 办法,这样原始数据会在任意一个 Buf 调用 release() 办法之后被回收。
如果不想有下面的副作用,那么能够将办法替换成 retainedDuplicate(), retainedSlice(), retainedSlice(int, int) 和 readRetainedSlice(int),这些办法会调用 retain() 办法以减少一个援用。
和现有 JDK 类型的转换
之前提到了 ByteBuf 是对 ByteBuffer 的重写,他们是不同的实现。尽管这两个不同,然而不障碍将 ByteBuf 转换 ByteBuffer。
当然,最简略的转换是把 ByteBuf 转换成 byte 数组 byte[]。要想转换成 byte 数组,能够先调用 hasArray() 进行判断,而后再调用 array() 办法进行转换。
同样的 ByteBuf 还能够转换成为 ByteBuffer,能够先调用 nioBufferCount() 判断可能转换成为 ByteBuffers 的个数,再调用 nioBuffer() 进行转换。
返回的 ByteBuffer 是对现有 buf 的共享或者复制,对返回之后 buffer 的 position 和 limit 批改不会影响到原 buf。
最初,应用 toString(Charset) 办法能够将 ByteBuf 转换成为 String。
总结
ByteBuf 是 netty 的底层根底,是传输数据的承载对象,深刻了解 ByteBuf 就能够搞懂 netty 的设计思维,十分不错。
本文的例子能够参考:learn-netty4
本文已收录于 http://www.flydean.com/02-netty-bytebuf/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!