byteBuf
byteBuf介绍
在介绍netty的byteBuf之前我们先来回顾一下nio的byteBuffer,byteBuffer内部维护了一个byte[]数组,而且提供了几个属性position、limit、capacity、mark四个属性来组织byteBuffer。
- position:当前操作的位置
- limit:最大可以读或者可写的位置
- capacity:最大的空间容量是多少,byteBuffer在创建时,就固定下来
- mark:标记当前的位置,下次可以回到这个位置
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
byteBuffer.putInt(1);
byteBuffer.putInt(2);
byteBuffer.flip();
byteBuffer.getInt(1);
当我们需要读取数据时需要执行flip操作完成模式的切换,将写入模式变为读取模式,其实就是将position的值赋值给limit,然后将position赋值为0就可以从position的位置开始到limit位置结束读取。在进行读写时频繁的执行flip操作进行指针的移动时可能或出现错误,并且byteBuffer在内存分配后不能改变,当读写内容大于缓存区大小的时候会发生越界异常。
netty的byteBuf是对byteBuffer的优化,同样用于网络IO的读写,使用起来比较简单,byteBuf维护了两个指针一个读指针和一个些指针,两个指针相互独立,互不影响,在写入数据的时候,只会移动写指针,当去读数据时只会移动读取指针,并且操作过程中会对读指针进行判断不能超过写指针,同时写指针如果超过了缓冲区大小,如果不够将进行扩容,我们来看下byteBuf的结构
<pre>
* +-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* | | (CONTENT) | |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
* </pre>
当读取数据时会移动读指针,当写数据时会移动写指针,而从0-readerIndex的长度就是已经读取过的长度,从readerIndex-writerIndex就是可以读取的长度,从writerIndex-capacity就是可以写入的长度,当写入不够的时候会进行扩容。
我们看下AbstractByteBuf的属性
int readerIndex;//读指针
int writerIndex;//写指针
private int markedReaderIndex;//记录读指针
private int markedWriterIndex;//记录写指针
private int maxCapacity;//最大容量
基本API介绍
byteBuf提供了很多方法由子类来实现,我们这里先举几个简单的例子
随机获取:
| 方法 | 说明 |
|---|---|
| getInt(int index) | 返回当前索引的整型 |
| getByte(int index) | 返回当前索引的字节 |
| getBytes(int index, ByteBuf dst) | 从索引位置获取字节到dst中 |
随机写入:
| 方法 | 说明 |
|---|---|
| setByte(int index, int value) | 在指定的索引位置设置 byte 值 |
| setInt(int index, int value) | 在指定索引位置设置int值 |
| setBytes(int index, ByteBuf src) | 在指定索引位置设置src |
顺序读:
| 方法 | 说明 |
|---|---|
| readInt() | 从读索引位置读取int值 |
| readBytes(int length) | 从读取索引位置读取length长数据 |
| readBoolean() | 读取一个布尔值,读索引+1 |
| 顺序写: | |
| 方法 | 说明 |
| – | – |
| writeInt(int value) | 从写入索引位置写入int值 |
| writeBytes(ByteBuf src) | 从写入索引位置写入src |
| writeBoolean(boolean value) | 从写入索引位置写入布尔值 |
副本:
| 方法 | 说明 |
|---|---|
| duplicate() | 从写入索引位置写入int值 |
| slice() | 从写入索引位置写入src |
| asReadOnly() | 从写入索引位置写入布尔值 |
| copy() | 复制一个byteBuf |
| copy(int index, int length) | 有数据独立副本 |
顺序写:
| 方法 | 说明 |
|---|---|
| ByteBuffer nioBuffer() | 转变为ByteBuffer |
| ByteBuffer[] nioBuffers() | 转变为ByteBuffer数组 |
byteBuf方法还有很多我们这里只是简单列举了一些,更多的方法可以自己到官网查看。
byteBuf分类
byteBuf有很多种分类,下面我们来看看
- 第一种:基于堆内存和直接内存划分
堆内存分配的byteBuf类名中会包含heap单词,比如PooledHeapByteBuf。堆内存分配是指分配的内存是在jvm内分配的,也就是会被jvm管理,存在垃圾回收
直接内存分配,类名中会包含direct单词,比如PooledDirectByteBuf,直接内存是指在jvm外申请的内存,他在垃圾回收机制作用范围之外,只有当指向直接内存的对象被回收时,堆外内存才会被回收,而堆外内存一般使用时间比较长,堆内的对象GC年龄达到了-XX:MaxTenuringThreshold的Young GC后就会进入老年代,而这些对象很小,但堆外申请的内存可能很大,当触发full GC的时候才会被清理掉,这样可能会导致堆外内存很难被回收。这里要考虑手动回收 ,通过执行Cleaner的clean方法清理掉堆外内存,执行unsafe的freeMemory方法。
- 第二种:基于池化和非池化
池化是指netty是会一次性申请一块默认16M大小的内存,然后根据申请内存的大小分为tiny、small、normal去申请一块连续的内存封装成byteBuf。类名中包含pooled单词,有关池化内存如果分配我们下面在做介绍。
非池化就是直接调用系统API进行实现,类名中包含unPooled单词
- 第三种:safe和unsafe
这里的safe和unsafe其实是指jdk中有一个unsafe类,同过unsafe类可以直接拿到内存地址,直接通过内存地址进行读写,这个是jdk底层的方法不对外进行暴露,都是一些native方法,只是提供了实现方法的定义和约束,真正的实现是通过C或者C++语言实现,用户使用这些api可能会有不安全的行为,所以这里代表是不安全的。这类byteBuf类名中会包含unsafe单词。
safe相反就是不能拿到jvm的具体内存进行操作,这里代表安全。这类byteBuf类名中会有safe单词。
byteBuf分配器
byteBuf分配器主要是用来分配byteBuf的工具类,他的顶级抽象是ByteBufAllocator接口,接口中定义了很多有关byteBuf的操作方法,该接口实现抽象类是AbstractByteBufAllocator类,他实现了接口的大部分功能,定义了分配byteBuf的骨架逻辑,其他实现交给子类具体实现
/**
* Skeletal {@link ByteBufAllocator} implementation to extend.
*/
public abstract class AbstractByteBufAllocator implements ByteBufAllocator
这个抽象类有两个实现类PooledByteBufAllocator和UnpooledByteBufAllocator,通过类名我们应该可以看得出这两个类一个是用来创建池化byteBuf的和非池化byteBuf的,具体的实现由子类重写。我们先来看看其中的几个方法
public ByteBuf buffer() {
//判断默认是不是申请直接内存
if (directByDefault) {
return directBuffer();
}
return heapBuffer();
}
public ByteBuf heapBuffer() {
//256 MAX_VALUE
return heapBuffer(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_CAPACITY);
}
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
//如果长度为0返回空的byteBuf
if (initialCapacity == 0 && maxCapacity == 0) {
return emptyBuf;
}
//验证传入的长度和最大容量
validate(initialCapacity, maxCapacity);
return newHeapBuffer(initialCapacity, maxCapacity);
}
protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);
这里可以看到,我们在没有传入申请内存块大小的时候,默认是申请256大小的byte,并且会判断是申请直接内存还是堆内存,最终实现由子类实现。
193



被折叠的 条评论
为什么被折叠?



