什么是ByteBuf
ByteBuf是Netty中非常重要的一个组件,他就像物流公司的运输工具:卡车,火车,甚至是飞机。而物流公司靠什么盈利,就是靠运输货物,可想而知ByteBuf在Netty中是多么的重要。没有了ByteBuf,Netty就失去了灵魂,其他所有的都将变得毫无意义。
ByteBuf是由Byte和Buffer两个词组合成的一个词,但是因为JDK中已经有了一个ByteBuffer,并且使用非常复杂,API及其不友好,可谓是千夫所指。为了扭转ByteBuffer在大家心目中的形象,Netty重新设计了一个ByteBuffer,即 ByteBuf
。
从字面上我们可以知道 ByteBuf
是处理字节的,并且还有一种缓冲的能力。
ByteBuf在官方中是这样定义的:
A random and sequential accessible sequence of zero or more bytes (octets).
This interface provides an abstract view for one or more primitive byte
arrays ({@code byte[]}) and {@linkplain ByteBuffer NIO buffers}.
就是说 ByteBuf
是一个字节序列,可以随机或连续存取零到多个字节。他提供了一个统一的抽象,通过 ByteBuf
可以操作基础的字节数组和ByteBuffer缓冲区。
需要注意的是这里说的 interface
是不准确的,因为Trustin Lee在2013/7/8将ByteBuffer从接口改成了抽象类,具体的原因不得而知。
ByteBuf的结构
ByteBuf比JDK中原生的ByteBuffer好的原因是前者的设计比后者优秀,ByteBuf有读和写两个指针,而ByteBuffer只有一个指针,需要通过flip()方法在读和写之间进行模式切换,需要操作的越多往往犯错的概率就越大。ByteBuf将读和写进行的分离,使用者不用再关心现在是读还是写的模式,可以把更多的精力用在具体的业务上。
官方定义中指出,ByteBuf主要是通过两个指针进行数据的读和写,分别是 readerIndex
和 writerIndex
,并且整个ByteBuf被这两个指针最多分成三个部分,分别是可丢弃部分,可读部分和可写部分,可以用一张图直观的描述ByteBuf的结构,如下图所示:
可能有人注意到了我说ByteBuf最多被分成三个部分,那是因为某些情况下可能只有一到两部分:
- 刚初始化的时候
刚初始化的时候,读写指针都是0,所有的内容都是可写部分,此时还没有可读部分和可丢弃部分。
- 刚写完数据后
刚写完一些数据后,读指针仍然是0,写指针向后移动了n
,这里的n
就是写入的字节数。
- 读完一部分数据并丢弃之后
写入完数据之后,紧接着读取一部分数据,然后立刻丢弃掉,此时ByteBuf的结构就会变成跟第二步中的一样。因为丢弃的动作会将读指针向左移动到0的位置,写指针向左移动的距离=原来读指针的值
ByteBuf的读写操作
写操作
ByteBuf中定义了两类方法可以往ByteBuf中写入内容:writeXX()
和 setXX()
。
具体的setXX()类的方法可以用下面的一张表格来描述:
方法名 | 描述 |
---|---|
setByte(int index, int value) | 将指定位置上的内容修改为指定的byte的值<br />高24位上的内容将被丢弃 |
setBoolean(int index, boolean value) | 将指定位置上的内容修改为指定的boolean的值 |
setBytes(int index,byte src) | 将指定的字节内容<br />可以从byte[],ByteBuf,ByteBuffer,InputStream,Channel等中获取<br />转移到指定的位置 |
setChar*(int index, int value) | 将指定位置上的内容修改为指定的character的UTF-16编码下2-byte的值<br />高16位上的内容将被丢弃 |
setShort*(int index, int value) | 将指定位置上的内容修改为指定的integer的低16-bit的值<br />高16位上的内容将被丢弃 |
setMidium*(int index, int value) | 将指定位置上的内容修改为指定的integer的中间24-bit的值<br />大多数重要的内容将被丢弃 |
setInt*(int index, int value) | 将指定位置上的内容修改为指定的32-bit的integer的值 |
setFloat*(int index, float value) | 将指定位置上的内容修改为指定的32-bit的float的值 |
setDouble*(int index, double value) | 将指定位置上的内容修改为指定的64-bit的float的值 |
setLong*(int index, long value) | 将指定位置上的内容修改为指定的64-bit的long的值 |
setZero(int index, int length) | 将从指定位置index开始之后的length个长度的值设置为0x00 |
我们知道java中一个int占4个字节,即32bit,一个short占2个字节,一个int可以拆成2个short,所以就会存在当写入一个short时,参数用int来传值时,高16位的内容会被丢弃。这是因为一个int被拆成了两个short,而写入一个short到指定的位置时,那么另一个short就被丢弃了,且是高16位的这个short。
有的人注意到了上面好多方法后面都有,这是表示这些方法还有一种兄弟方法,如setInt对应的是setIntLE,这表示以小端字节序*的方式写入内容。简单来说一般网络传输采用大端字节序,另外我们人类写字节的顺序也是大端字节序,而计算机处理字节的顺序一般是小端字节序(但是也不绝对,计算机从低电平开始读取字节时效率更高),具体什么是大端字节序,什么是小端字节序不是本篇文章深入研究的范围,大家可以自行查阅有关资料。
PS:需要注意的是如果写入的位置index小于0,或者index加上写入内容的值超过capcity的话,会抛出 IndexOutOfBoundsException
,所以就存在两个比较重要的方法:isWritable()
,isReadable()
,他们将返回当前ByteBuf中是否还有足够的空间可以写和可以读
具体的writeXX()方法与上面的setXX()方法类似,不同的是writeXX()方法会更新写指针,即向ByteBuf中写入具体的内容后,writeIndex会向后移动与写入的内容字节数长度相同的距离。
读操作
跟写操作一样,ByteBuf的读操作也有两种方法,分别是getXX()和readXX()。
读操作包含的具体方法与写操作也是一一对应的,具体的可以把上面的那张表格中的set改为get,并且将第二个value参数移除即可,例如:getShort(int index)
,getInt(int index)
等等。
与getXX()方法相关的另一类方法就是readXX()方法了,与get方法不同的是,read方法会更改读指针的值。
ByteBuf的种类
我们知道ByteBuf在4.x的版本中是一个抽象类,他有很多的抽象子类以及各种实现类。
画了一个简单的ByteBuf的各个实现类之间的关系,其中蓝色的类是被弃用的。
image-byte-buf-hierarchy.png上图只是简单的列举的一些常用的ByteBuf类,如果你想知道ByteBuf所有的实现类,那么可以在IDEA中选
则ByteBuf类之后,然后在菜单 navigate
中点击 Type Hierarchy
或用快捷键:control+H,即可打开ByteBuf的类层次结构图,具体的层级结构如下图所示:
本篇文章只简单的让大家对于ByteBuf的种类有个大概的了解,具体的每一种ByteBuf的作用我将在后续的章节中进行介绍。
欢迎关注微信公众号我是逅弈,如果文章对您有帮助,欢迎您点赞加关注,并欢迎您关注我的公众号:
网友评论