简介
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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!