怎么完整下载网站模板,vscode 网站开发教程,php做动漫网站,广州做外贸网站建设ByteBuf是什么ByteBuf是Netty中非常重要的一个组件#xff0c;他就像物流公司的运输工具#xff1a;卡车#xff0c;火车#xff0c;甚至是飞机。而物流公司靠什么盈利#xff0c;就是靠运输货物#xff0c;可想而知ByteBuf在Netty中是多么的重要。没有了ByteBuf#xf…ByteBuf是什么ByteBuf是Netty中非常重要的一个组件他就像物流公司的运输工具卡车火车甚至是飞机。而物流公司靠什么盈利就是靠运输货物可想而知ByteBuf在Netty中是多么的重要。没有了ByteBufNetty就失去了灵魂其他所有的都将变得毫无意义。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 bytearrays ({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的值高24位上的内容将被丢弃setBoolean(int index, boolean value)将指定位置上的内容修改为指定的boolean的值setBytes(int index,byte src)将指定的字节内容可以从byte[],ByteBuf,ByteBuffer,InputStream,Channel等中获取转移到指定的位置setChar*(int index, int value)将指定位置上的内容修改为指定的character的UTF-16编码下2-byte的值高16位上的内容将被丢弃setShort*(int index, int value)将指定位置上的内容修改为指定的integer的低16-bit的值高16位上的内容将被丢弃setMidium*(int index, int value)将指定位置上的内容修改为指定的integer的中间24-bit的值大多数重要的内容将被丢弃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的各个实现类之间的关系其中蓝色的类是被弃用的。上图只是简单的列举的一些常用的ByteBuf类如果你想知道ByteBuf所有的实现类那么可以在IDEA中选则ByteBuf类之后然后在菜单 navigate 中点击 Type Hierarchy 或用快捷键controlH即可打开ByteBuf的类层次结构图具体的层级结构如下图所示本篇文章只简单的让大家对于ByteBuf的种类有个大概的了解具体的每一种ByteBuf的作用我将在后续的章节中进行介绍。ByteBuf的使用有一点我们需要知道的是ByteBuf的jar包是可以单独使用的。比如某个项目中有一个场景需要处理某个自定义的协议那么我们在解析协议时就可以将接收到的将字节内容写入一个ByteBuf然后从ByteBuf中慢慢的将内容读取出来。下面让我们用一个例子简单的了解下ByteBuf的使用。ByteBuf的创建要想使用ByteBuf首先肯定是要创建一个ByteBuf更确切的说法就是要申请一块内存后续可以在这块内存中执行写入数据读取数据等等一系列的操作。那么如何创建一个ByteBuf呢Netty中设计了一个专门负责分配ByteBuf的接口ByteBufAllocator。该接口有一个抽象子类和两个实现类分别对应了用来分配池化的ByteBuf和非池化的ByteBuf。具体的层级关系如下图所示有了Allocator之后Netty又为我们提供了两个工具类Pooled、Unpooled分类用来分配池化的和未池化的ByteBuf进一步简化了创建ByteBuf的步骤只需要调用这两个工具类的静态方法即可。不同的创建方法我们以Unpooled类为例查看Unpooled的源码可以发现他为我们提供了许多创建ByteBuf的方法但最终都是以下这几种只是参数不一样而已// 在堆上分配一个ByteBuf并指定初始容量和最大容量public static ByteBuf buffer(int initialCapacity, int maxCapacity) {return ALLOC.heapBuffer(initialCapacity, maxCapacity);}// 在堆外分配一个ByteBuf并指定初始容量和最大容量public static ByteBuf directBuffer(int initialCapacity, int maxCapacity) {return ALLOC.directBuffer(initialCapacity, maxCapacity);}// 使用包装的方式将一个byte[]包装成一个ByteBuf后返回public static ByteBuf wrappedBuffer(byte[] array) {if (array.length 0) {return EMPTY_BUFFER;}return new UnpooledHeapByteBuf(ALLOC, array, array.length);}// 返回一个组合ByteBuf并指定组合的个数public static CompositeByteBuf compositeBuffer(int maxNumComponents){return new CompositeByteBuf(ALLOC, false, maxNumComponents);}其中包装方法除了上述这个方法之外还有一些其他常用的包装方法比如参数是一个ByteBuf的包装方法比如参数是一个原生的ByteBuffer的包装方法比如指定一个内存地址和大小的包装方法等等。另外还有一些copy*开头的方法实际是调用了buffer(int initialCapacity, int maxCapacity)或directBuffer(int initialCapacity, int maxCapacity)方法然后将具体的内容write进生成的ByteBuf中返回。以上所有的这些方法都实际通过一个叫ALLOC的静态变量进行了调用来实现具体的ByteBuf的创建而这个ALLOC实际是一个ByteBufAllocator:private static final ByteBufAllocatorALLOC UnpooledByteBufAllocator.DEFAULT;ByteBufAllocator是一个专门负责ByteBuf分配的接口对应的Unpooled实现类就是UnpooledByteBufAllocator。在UnpooledByteBufAllocator类中可以看到UnpooledByteBufAllocator.DEFAULT变量是一个final类型的静态变量/*** Default instance which uses leak-detection for direct buffers.* 默认的UnpooledByteBufAllocator实例并且会对堆外内存进行泄漏检测*/public static final UnpooledByteBufAllocatorDEFAULT new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred());涉及的设计模式ByteBuf和ByteBufAllocator之间是一种相辅相成的关系ByteBufAllocator用来创建一个ByteBuf而ByteBuf亦可以返回创建他的Allocator。ByteBuf和ByteBufAllocator之间是一种 抽象工厂模式具体可以用一张图描述如下下面我来用一个实际的例子来说明ByteBuf的使用并通过观察在不同阶段ByteBuf的读写指针的值和ByteBuf的容量变化来更加深入的了解ByteBuf的设计为了方便我会用非池化的分配器来创建ByteBuf。使用示例我构造了一个demo来演示在ByteBuf中插入数据、读取数据、清空读写指针、数据清零、扩容等等方法具体的代码如下private static void simpleUse(){// 1.创建一个非池化的ByteBuf大小为10个字节ByteBuf buf Unpooled.buffer(10);System.out.println(原始ByteBuf为buf.toString());System.out.println(1.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 2.写入一段内容byte[] bytes {1,2,3,4,5};buf.writeBytes(bytes);System.out.println(写入的bytes为Arrays.toString(bytes));System.out.println(写入一段内容后ByteBuf为buf.toString());System.out.println(2.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 3.读取一段内容byte b1 buf.readByte();byte b2 buf.readByte();System.out.println(读取的bytes为Arrays.toString(new byte[]{b1,b2}));System.out.println(读取一段内容后ByteBuf为buf.toString());System.out.println(3.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 4.将读取的内容丢弃buf.discardReadBytes();System.out.println(将读取的内容丢弃后ByteBuf为buf.toString());System.out.println(4.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 5.清空读写指针buf.clear();System.out.println(将读写指针清空后ByteBuf为buf.toString());System.out.println(5.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 6.再次写入一段内容比第一段内容少byte[] bytes2 {1,2,3};buf.writeBytes(bytes2);System.out.println(写入的bytes为Arrays.toString(bytes2));System.out.println(写入一段内容后ByteBuf为buf.toString());System.out.println(6.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 7.将ByteBuf清零buf.setZero(0,buf.capacity());System.out.println(将内容清零后ByteBuf为buf.toString());System.out.println(7.ByteBuf中的内容为Arrays.toString(buf.array())\n);// 8.再次写入一段超过容量的内容byte[] bytes3 {1,2,3,4,5,6,7,8,9,10,11};buf.writeBytes(bytes3);System.out.println(写入的bytes为Arrays.toString(bytes3));System.out.println(写入一段内容后ByteBuf为buf.toString());System.out.println(8.ByteBuf中的内容为Arrays.toString(buf.array())\n);}执行结果如下原始ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 10)1.ByteBuf中的内容为[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]写入的bytes为[1, 2, 3, 4, 5]写入一段内容后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 5, cap: 10)2.ByteBuf中的内容为[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]读取的bytes为[1, 2]读取一段内容后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 2, widx: 5, cap: 10)3.ByteBuf中的内容为[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]将读取的内容丢弃后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 3, cap: 10)4.ByteBuf中的内容为[3, 4, 5, 4, 5, 0, 0, 0, 0, 0]将读写指针清空后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 10)5.ByteBuf中的内容为[3, 4, 5, 4, 5, 0, 0, 0, 0, 0]写入的bytes为[1, 2, 3]写入一段内容后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 3, cap: 10)6.ByteBuf中的内容为[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]将内容清零后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 3, cap: 10)7.ByteBuf中的内容为[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]写入的bytes为[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]写入一段内容后ByteBuf为UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 14, cap: 64)8.ByteBuf中的内容为[0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]执行过程分析下面让我们来仔细的研究下执行的过程并分析下为什么会产生这样的执行结果。1.初始化一个大小为10的ByteBuf刚初始化的ByteBuf对象容量为10读写指针都为0且每个字节的值都为0并且这些字节都是“可写”的我们用红色来表示。2.写入一段内容当写入一段内容后(这里写入的是5个字节)写指针向后移动了5个字节写指针的值变成了5而读指针没有发生变化还是0但是读指针和写指针之间的字节现在变成了“可读”的状态了我们用紫色来表示。3.读取一段内容接着我们有读取了2个字节的内容这时读指针向后移动了2个字节读指针的值变成了2写指针不变此时0和读指针之间的内容变成了“可丢弃”的状态了我们用粉色来表示。4.将读取的内容丢弃紧接着我们将刚刚读取完的2个字节丢弃掉这时ByteBuf把读指针与写指针之间的内容(即 3、4、5 三个字节)移动到了0的位置并且将读指针更新为0写指针更新为原来写指针的值减去原来读指针的值。但是需要注意的是第4和第5个字节的位置上还保留的原本的内容只是这两个字节由原来的“可读”变成了现在的“可写”。5.将读写指针清空然后我们执行了一个 clear 方法将读写指针同时都置为0了此时所有的字节都变成“可写”了但是需要注意的是clear方法只是更改的读写指针的值每个位置上原本的字节内容并没有发生改变。6.再次写入一段较少的内容然后再次写入一段内容后读指针不变写指针向后移动了具体的字节数这里是向后移动了三个字节。且写入的这三个字节变成了“可读”状态。7.将ByteBuf中的内容清零清零(setZero)和清空(clear)的方法是两个概念完全不同的方法“清零”是把指定位置上的字节的值设置为0除此之外不改变任何的值所以读写指针的值和字节的“可读写”状态与上次保持一致而“清空”则只是将读写指针都置为0并且所有字节都变成了“可写”状态。8.写入一段超过容量的内容最后我们往ByteBuf中写入超过ByteBuf容量的内容这里是写入了11个字节此时ByteBuf原本的容量不足以写入这些内容了所以ByteBuf发生了扩容。其实只要写入的字节数超过可写字节数就会发生扩容了。扩容分析那么扩容是怎么扩的呢为什么容量从10扩容到64呢我们从源码中找答案。扩容肯定发生在写入字节的时候让我们找到 writeBytes(byte[] bytes) 方法具体如下Overridepublic ByteBuf writeBytes(byte[] src) {writeBytes(src, 0, src.length);return this;}Overridepublic ByteBuf writeBytes(byte[] src, int srcIndex, int length) {// 该方法检查是否有足够的可写空间是否需要进行扩容ensureWritable(length);setBytes(writerIndex, src, srcIndex, length);writerIndex length;return this;}在进入 ensureWritable(length) 方法内部查看具体的代码如下Overridepublic ByteBuf ensureWritable(int minWritableBytes) {if (minWritableBytes 0) {throw new IllegalArgumentException(String.format(minWritableBytes: %d (expected: 0), minWritableBytes));}ensureWritable0(minWritableBytes);return this;}final void ensureWritable0(int minWritableBytes) {// 检查该ByteBuf对象的引用计数是否为0保证该对象在写入之前是可访问的ensureAccessible();if (minWritableBytes writableBytes()) {return;}if (minWritableBytes maxCapacity - writerIndex) {throw new IndexOutOfBoundsException(String.format(writerIndex(%d) minWritableBytes(%d) exceeds maxCapacity(%d): %s,writerIndex, minWritableBytes, maxCapacity, this));}// Normalize the current capacity to the power of 2.// 计算新的容量即为当前容量扩容至2的幂次方大小int newCapacity alloc().calculateNewCapacity(writerIndex minWritableBytes, maxCapacity);// Adjust to the new capacity.// 设置扩容后的容量capacity(newCapacity);}从上面的代码中可以很清楚的看出来计算新的容量的方法是调用的 ByteBufAllocator 的 calculateNewCapacity() 方法继续跟进去该方法这里的 ByteBufAllocator 的实现类是 AbstractByteBufAllocator 具体的代码如下Overridepublic int calculateNewCapacity(int minNewCapacity, int maxCapacity) {if (minNewCapacity 0) {throw new IllegalArgumentException(minNewCapacity: minNewCapacity (expected: 0));}if (minNewCapacity maxCapacity) {throw new IllegalArgumentException(String.format(minNewCapacity: %d (expected: not greater than maxCapacity(%d),minNewCapacity, maxCapacity));}// 扩容的阈值4兆字节大小final int threshold CALCULATE_THRESHOLD; // 4 MiB pageif (minNewCapacity threshold) {return threshold;}// If over threshold, do not double but just increase by threshold.// 如果要扩容后新的容量大于扩容的阈值那么扩容的方式改为用新的容量加上阈值// 否则将新容量改为双倍大小进行扩容if (minNewCapacity threshold) {int newCapacity minNewCapacity / threshold * threshold;if (newCapacity maxCapacity - threshold) {newCapacity maxCapacity;} else {newCapacity threshold;}return newCapacity;}// Not over threshold. Double up to 4 MiB, starting from 64.// 如果要扩容后新的容量小于4兆字节则从64字节开始扩容每次双倍扩容// 直到小于指定的新容量位置int newCapacity 64;while (newCapacity minNewCapacity) {newCapacity 1;}return Math.min(newCapacity, maxCapacity);}到这里就很清楚了每次扩容时有一个阈值t(4MB)计划扩容的大小为c扩容后的值为n。扩容的规则可以用下面的逻辑表示如果c如果ct则nc/t*tt得出的结论ByteBuf有读和写两个指针用来标记“可读”、“可写”、“可丢弃”的字节调用write*方法写入数据后写指针将会向后移动调用read*方法读取数据后读指针将会向后移动写入数据或读取数据时会检查是否有足够多的空间可以写入和是否有数据可以读取写入数据之前会进行容量检查当剩余可写的容量小于需要写入的容量时需要执行扩容操作扩容时有一个4MB的阈值需要扩容的容量小于阈值或大于阈值所对应的扩容逻辑不同clear等修改读写指针的方法只会更改读写指针的值并不会影响ByteBuf中已有的内容setZero等修改字节值的方法只会修改对应字节的值不会影响读写指针的值以及字节的可读写状态