> Netty 在数据传输过程当中,会使用缓冲区设计来提升传输效率。虽然,Java 在 NIO 编程中已提供 ByteBuffer 类进行使用,可是在使用过程当中,其编码方式相对来讲不太友好,也存在必定的不足。因此高性能的 Netty 框架实现了一套更增强大,完善的 ByteBuf,其设计理念也是堪称一绝。java
在分析 ByteBuf 以前,先简单讲下 ByteBuffer 类的操做。便于更好理解 ByteBuf 。编程
ByteBuffer 的读写操做共用一个位置指针,读写过程经过如下代码案例分析:数组
// 分配一个缓冲区,并指定大小 ByteBuffer buffer = ByteBuffer.allocate(100); // 设置当前最大缓存区大小限制 buffer.limit(15); System.out.println(String.format("allocate: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); String content = "ytao公众号"; // 向缓冲区写入数据 buffer.put(content.getBytes()); System.out.println(String.format("put: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));
其中打印了缓冲区三个参数,分别是:缓存
打印结果:框架
当咱们写入内容后,读写指针值为 13,ytao公众号
英文字符占 1 个 byte,每一个中文占 4 个 byte,恰好 13,小于设置的当前缓冲区大小 15。函数
接下来,读取内容里的 ytao 数据:性能
buffer.flip(); System.out.println(String.format("flip: pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); byte[] readBytes = new byte[4]; buffer.get(readBytes); System.out.println(String.format("get(4): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity())); String readContent = new String(readBytes); System.out.println("readContent:"+readContent);
读取内容须要建立个 byte 数组来接收,并制定接收的数据大小。编码
在写入数据后再读取内容,必须主动调用ByteBuffer#flip
或ByteBuffer#clear
。设计
ByteBuffer#flip
它会将写入数据后的指针位置值做为当前缓冲区大小,再将指针位置归零。会使写入数据的缓冲区改成待取数据的缓冲区,也就是说,读取数据会从刚写入的数据第一个索引做为读取数据的起始索引。3d
ByteBuffer#flip
相关源码:
ByteBuffer#clear
则会重置 limit 为默认值,与 capacity 大小相同。
接下读取剩余部份内容:
第二次读取的时候,可以使用buffer#remaining
来获取大于或等于剩下的内容的字节大小,该函数实现为limit - position
,因此当前缓冲区域必定在这个值范围内。
readBytes = new byte[buffer.remaining()]; buffer.get(readBytes); System.out.println(String.format("get(remaining): pos=%s lim=%s cap=%s", buffer.position(), buffer.limit(), buffer.capacity()));
打印结果:
以上操做过程当中,索引变化如图:
ByteBuf 有读写指针是分开的,分别是buf#readerIndex
和buf#writerIndex
,当前缓冲器大小buf#capacity
。
这里缓冲区被两个指针索引和容量划分为三个区域:
以下图所示:
ByteBuf 分配一个缓冲区,仅仅给定一个初始值就能够。默认是 256。初始值不像 ByteBuffer 同样是最大值,ByteBuf 的最大值是Integer.MAX_VALUE
ByteBuf buf = Unpooled.buffer(13); System.out.println(String.format("init: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印结果:
ByteBuf 写操做和 ByteBuffer 相似,只是写指针是单独记录的,ByteBuf 的写操做支持多种类型,有如下多个API:
写入字节数组类型:
String content = "ytao公众号"; buf.writeBytes(content.getBytes()); System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印结果:
索引示意图:
同样的,ByteBuf 写操做和 ByteBuffer 相似,只是写指针是单独记录的,ByteBuf 的读操做支持多种类型,有如下多个API:
从当前 readerIndex 位置读取四个字节内容:
byte[] dst = new byte[4]; buf.readBytes(dst); System.out.println(new String(dst)); System.out.println(String.format("read(4): ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印结果:
索引示意图:
经过上面的 ByteBuffer 分配缓冲区例子,向里面添加 [ytao公众号ytao公众号] 内容,使写入的内容大于 limit 的值。
ByteBuffer buffer = ByteBuffer.allocate(100); buffer.limit(15); String content = "ytao公众号ytao公众号"; buffer.put(content.getBytes());
运行结果异常:
内容字节大小超过了 limit 的值时,缓冲区溢出异常,因此咱们每次写入数据前,得检查缓区大小是否有足够空间,这样对编码上来讲,不是一个好的体验。
使用 ByteBuf 添加一样的内容,给定一样的初始容器大小。
ByteBuf buf = Unpooled.buffer(15); String content = "ytao公众号ytao公众号"; buf.writeBytes(content.getBytes()); System.out.println(String.format("write: ridx=%s widx=%s cap=%s", buf.readerIndex(), buf.writerIndex(), buf.capacity()));
打印运行结果:
经过上面打印信息,能够看到 cap 从设置的 15 变为了 64,当咱们容器大小不够时,就是进行扩容,接下来咱们分析扩容过程当中是如何作的。 进入 writeBytes 里面:
校验写入内容长度:
在可写区域检查里:
writableBytes()
函数为可写区域大小capacity - writerIndex
在容量不足,从新分配缓冲区的里面,以 4M 为阀门:
> Netty 实现的缓冲区,八个基本类型中,除了布尔类型,其余7种都有本身对应的 Buffer,可是实际使用过程当中, ByteBuf 才是咱们尝试用的,它可兼容任何类型。ByteBuf 在 Netty 体系中是最基础也是最重要的一员,要想更好掌握和使用 Netty,先理解并掌握 ByteBuf 是必需条件之一。
我的博客: https://ytao.top
关注公众号 【ytao】,更多原创好文