Netty之ByteBuf与jdk的ByteBuffer区别
使用NIO进行文件读取所涉及的步骤:
从FileInputStream对象获取到Channel对象。
创建Buffer。
将数据从Channel中读取到Buffer对象中。
mark<=position<=limit<=capacity
flip()方法:
- 将limit值设为当前position。
- 将position值设置为0。
clear()方法:
- 将limit值设为capacity。
- 将position值设为0。
compact()方法:
- 将所有未读的数据复制到buffer的起始位置处。
- 将position设为最后一个未读元素的后面。
- 将limit设为capacity。
- 现在buffer就准备好了,但是不会覆盖未读的数据。
rewind()方法:将position置为0,mark丢弃。
注意:通过索引访问Byte时并不会改变真实的读索引和写索引;我们可以通过ByteBuf的readerIndex()与writeIndex()方法分别修改读索引和写索引。 Netty ByteBuf所提供的三种缓冲区类型:
- heap buffer
- direct buffer
- composite buffer
Heap Buffer(堆缓冲区) 这是最常用的类型,ByteBuf将数据存储在JVM的堆空间中,并且将实际的数据存放到byte array中来实现。 优点:由于数据是存储在JVM堆中,因此可以快速地创建与快速的释放,并且它提供了直接访问内部字节数据数组的方法。 缺点:每次读写数据时,都需要先将数据复制到直接缓冲区再进行网络传输。
Direct Buffer(直接缓存区)
在堆之外直接分配内存空间,直接缓冲区并不会占用堆的容量空间,因为它是有操作系统在本地内存进行的数据分配。
优点:在使用Socket进行数据传递时,性能非常好,因为数据直接位于操作系统的本地内存中,所以不需要从JVM将数据复制到直接缓冲区中,性能很好。
缺点:因为Direct Buffer是直接在操作系统内存中的,所以内存空间的分配与释放要比堆空间更加复杂,所以速度要慢一些。
Netty通过提供内存池来解决这个问题。直接缓冲区并不支持通过字节数组的方式来访问数据。
总结:对于后端的业务消息的编解码来说,推荐使用HeapByteBuf;对于I/O通信线程在读写缓冲区时,推荐使用DirectByteBuf。
Composite Buffer(复合缓冲区)
JDK的ByteBuffer与Netty的ByteBuf之间的差异比对:
- Netty的ByteBuf采用了读写索引分离的策略(readerIndex和writerIndex),一个初始化的ByteBuf的readerIndex和writerIndex值都为0。
- 当读索引与写索引处于同一个位置时,如果我们继续读取那么就会抛出IndexOutofBoundsException。
- 对于ByteBuf的任何读写操作都会分别单独维护读索引和写索引。maxCapacity最大容量默认的限制就是Integer.MAX_VALUE。
JDK的ByteBuffer的缺点:
- final byte[] hb;这是JDK的ByteBuffer对象中用于存储数据的对象声明:可以看到,其字节数组是被声明为final的,也就是长度是固定不变的,一旦分配好内存空间后不能动态扩容和收缩;而且当待存储的数据字节很大时就很有可能出现IndexOutofBoundsException。如果要预防这个异常,那就需要在存储之前完全确定好待存储的字节大小。如果ByteBuffer的空间不足,我们只有一种解决方案:创建一个全新的ByteBuffer对象,然后再将之前的ByteBuffer对象中的数据复制过去,这一切操作都需要由开发者自己来手动完成。
- ByteBuffer只使用一个position指针来标识位置信息,再进行读写切换时就需要调用flip方法或是rewind方法,使用起来很不方便。
Netty的ByteBuf的优点:
- 存储字节的数组是动态的。其最大值默认是Integer.MAX_VALUE.这里的动态性是体现在write方法中的,write方法在执行时会判断buffer容量,如果不足则自动扩容。
- ByteBuf的读写索引是完全分离开的,使用起来很方便。