目录html
疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之16 【 博客园 总入口 】java
源码IDEA工程获取连接:Java 聊天室 实战 源码面试
你们好,我是做者尼恩。api
今天是百万级流量 Netty 聊天器 打造的系列文章的第16篇,这是一个基础篇,介绍ByteBuf 的使用。数组
因为关于ByteBuf的内容比较多,分两篇文章:分布式
第一篇:图解 ByteBuf的分配、释放和如何避免内存泄露this
第二篇:图解 ByteBuf的具体使用.net
本篇为第二篇。3d
ByteBuf 是一个字节容器,内部是一个字节数组。指针
从逻辑上来分,字节容器内部,能够分为四个部分:
第一个部分是已经丢弃的字节,这部分数据是无效的;
第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;
第三部分的数据是可写字节,全部写到 ByteBuf 的数据都会写到这一段。
第四部分的字节,表示的是该 ByteBuf 最多还能扩容的大小。
四个部分的逻辑功能,以下图所示:
ByteBuf 经过三个整型的指针(index),有效地区分可读数据和可写数据,使得读写之间相互没有冲突。
这个三个指针,分别是:
这三个指针,是三个int 型的成员属性,定义在 AbstractByteBuf 抽象基类中。
三个指针的代码截图,以下:
readerIndex 读指针
指示读取的起始位置。
每读取一个字节,readerIndex 自增1 。一旦 readerIndex 与 writerIndex 相等,ByteBuf 不可读 。
writerIndex 写指针
指示写入的起始位置。
每写一个字节,writerIndex 自增1。一旦增长到 writerIndex 与 capacity() 容量相等,表示 ByteBuf 已经不可写了 。
capacity()容量不是一个成员属性,是一个成员方法。表示 ByteBuf 内部的总容量。 注意,这个不是最大容量。
maxCapacity 最大容量
指示能够 ByteBuf 扩容的最大容量。
当向 ByteBuf 写数据的时候,若是容量不足,能够进行扩容。
扩容的最大限度,直到 capacity() 扩容到 maxCapacity为止,超过 maxCapacity 就会报错。
capacity()扩容的操做,是底层自动进行的。
从三个维度三大系列,介绍ByteBuf 的经常使用 API 方法。
方法 一:capacity()
表示 ByteBuf 的容量,包括丢弃的字节数、可读字节数、可写字节数。
方法二:maxCapacity()
表示 ByteBuf 底层最大可以占用的最大字节数。当向 ByteBuf 中写数据的时候,若是发现容量不足,则进行扩容,直到扩容到 maxCapacity。
方法一:isWritable()
表示 ByteBuf 是否可写。若是 capacity() 容量大于 writerIndex 指针的位置 ,则表示可写。不然为不可写。
isWritable()的源码,也是很简单的。具体以下:
public boolean isWritable() { return this.capacity() > this.writerIndex; }
注意:若是 isWritable() 返回 false,并不表明不能往 ByteBuf 中写数据了。 若是Netty发现往 ByteBuf 中写数据写不进去的话,会自动扩容 ByteBuf。
方法二:writableBytes()
返回表示 ByteBuf 当前可写入的字节数,它的值等于 capacity()- writerIndex。
以下图所示:
方法三:maxWritableBytes()
返回可写的最大字节数,它的值等于 maxCapacity-writerIndex 。
方法四:writeBytes(byte[] src)
把字节数组 src 里面的数据所有写到 ByteBuf。
这个是最为经常使用的一个方法。
方法五:writeTYPE(TYPE value) 基础类型写入方法
基础数据类型的写入,包含了 8大基础类型的写入。
具体以下:writeByte()、 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() ,向 ByteBuf写入基础类型的数据。
方法六:setTYPE(TYPE value)基础类型写入,不改变指针值
基础数据类型的写入,包含了 8大基础类型的写入。
具体以下:setByte()、 setBoolean()、setChar()、setShort()、setInt()、setLong()、setFloat()、setDouble() ,向 ByteBuf 写入基础类型的数据。
setType 系列与writeTYPE系列的不一样:
setType 系列 不会 改变写指针 writerIndex ;
writeTYPE系列 会 改变写指针 writerIndex 的值。
方法七:markWriterIndex() 与 resetWriterIndex()
这里两个方法一块儿介绍。
前一个方法,表示把当前的写指针writerIndex 保存在 markedWriterIndex 属性中;
后一个方法,表示把当前的写指针 writerIndex 恢复到以前保存的 markedWriterIndex 值 。
标记 markedWriterIndex 属性, 定义在 AbstractByteBuf 抽象基类中。
截图以下:
方法一:isReadable()
表示 ByteBuf 是否可读。若是 writerIndex 指针的值大于 readerIndex 指针的值 ,则表示可读。不然为不可写。
isReadable()的源码,也是很简单的。具体以下:
public boolean isReadable() { return this.writerIndex > this.readerIndex; }
方法二:readableBytes()
返回表示 ByteBuf 当前可读取的字节数,它的值等于 writerIndex - readerIndex 。
以下图所示:
方法三: readBytes(byte[] dst)
把 ByteBuf 里面的数据所有读取到 dst 字节数组中,这里 dst 字节数组的大小一般等于 readableBytes() 。 这个方法,也是最为经常使用的一个方法。
方法四:readType() 基础类型读取
基础数据类型的读取,能够读取 8大基础类型。
具体以下:readByte()、readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() ,从 ByteBuf读取对应的基础类型的数据。
方法五:getTYPE(TYPE value)基础类型读取,不改变指针值
基础数据类型的读取,能够读取 8大基础类型。
具体以下:getByte()、 getBoolean()、getChar()、getShort()、getInt()、getLong()、getFloat()、getDouble() ,从 ByteBuf读取对应的基础类型的数据。
getType 系列与readTYPE系列的不一样:
getType 系列 不会 改变读指针 readerIndex ;
readTYPE系列 会 改变读指针 readerIndex 的值。
方法六:markReaderIndex() 与 resetReaderIndex()
这里两个方法一块儿介绍。
前一个方法,表示把当前的读指针ReaderIndex 保存在 markedReaderIndex 属性中。
后一个方法,表示把当前的读指针 ReaderIndex 恢复到以前保存的 markedReaderIndex 值 。
标记 markedReaderIndex 属性, 定义在 AbstractByteBuf 抽象基类中。
截图以下:
Netty 的 ByteBuf 的内存回收工做,是经过引用计数的方式管理的。
大体的引用计数的规则以下:
若是引用为0,再次访问这个 ByteBuf 对象,将会抛出异常。
若是引用为0,表示这个 ByteBuf 没有地方被引用到,须要回收内存。
Netty的内存回收分为两种状况:
ByteBuf 的浅层复制分为两种,有切片slice 浅层复制,和duplicate 浅层复制。
首先说明一下,这是一种很是重要的操做。能够很大程度的避免内存拷贝。这一点,对于大规模消息通信来讲,是很是重要的。
slice 操做能够获取到一个 ByteBuf 的一个切片。一个ByteBuf,能够进行屡次的切片操做,多个切片能够共享一个存储区域的 ByteBuf 对象。
slice 操做方法有两个重载版本:
public ByteBuf slice(int index, int length);
两个版本有很是紧密的联系。
不带参数的 slice 方法,等同于 buf.slice(buf.readerIndex(), buf.readableBytes()) 调用, 即返回 ByteBuf 实例中可读部分的切片。
而带参数 slice(int index, int length) 方法,能够经过灵活的设置不一样的参数,来获取到 buf 的不一样区域的切片。
调用slice()方法后,返回的 ByteBuf 的切片,大体以下图:
调用slice()方法后,返回的ByteBuf 切片的属性,大体以下:
slice 的 readerIndex(读指针)的值为 0
slice 的 writerIndex(写指针) 的 值为源Bytebuf的 readableBytes() 可读字节数。
slice 的 maxCapacity(最大容量) 的值为源Bytebuf的 readableBytes() 可读字节数。maxCapacity 与 writerIndex 值相同,切片不能够写。
切片的可读字节数,为本身的 writerIndex - readerIndex。全部,切片和源Bytebuf的 readableBytes() 可读字节数相同。
也就是说,切片可读,不可写。
slice()切片和原ByteBuf的联系:
根本上,调用slice()方法生成的切片,是 源Bytebuf 可读部分的浅层复制。
下面的例子展现了 ByteBuf.slice 方法的演示:
public static void testSlice() { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100); print("allocate ByteBuf(9, 100)", buffer); buffer.writeBytes(new byte[]{1, 2, 3, 4}); print("writeBytes(1,2,3,4)", buffer); ByteBuf buffer1= buffer.slice(); print("buffer slice", buffer1); }
结果以下:
after ===========allocate ByteBuf(9, 100)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 0 isReadable(): false writerIndex(): 0 writableBytes(): 9 isWritable(): true maxWritableBytes(): 100 after ===========writeBytes(1,2,3,4)============ capacity(): 9 maxCapacity(): 100 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 5 isWritable(): true maxWritableBytes(): 96 after ===========buffer slice============ capacity(): 4 maxCapacity(): 4 readerIndex(): 0 readableBytes(): 4 isReadable(): true writerIndex(): 4 writableBytes(): 0 isWritable(): false maxWritableBytes(): 0
duplicate() 返回的是源ByteBuf 的整个对象的一个浅层复制,包括以下内容:
duplicate() 和slice() 方法,都是浅层复制。不一样的是,slice() 方法是切取一段的浅层复制,duplicate() 是整个的浅层复制。
浅层复制方法不会拷贝数据,也不会改变 ByteBuf 的引用计数,这就会致使一个问题。
在源 ByteBuf 调用 release() 以后,引用计数为零,变得不能访问。这个时候,源 ByteBuf 的浅层复制实例,也不能进行读写。若是再对浅层复制实例进行读写,就会报错。
所以,在调用浅层复制实例时,能够经过调用一次 retain() 方法 来增长引用,表示它们对应的底层的内存多了一次引用,引用计数为2,在浅层复制实例用完后,须要调用两次 release() 方法,将引用计数减一,不影响源ByteBuf的内存释放。
至此为止,终于完成ByteBuf的具体使用B介绍。
若是想知道ByteBuf的分配、释放, 请看:
Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
疯狂创客圈 【 博客园 总入口 】