原创

图解ByteBuffer和ByteBuf

全网最全 Java 知识点整合总目录入口猛戳-->www.gameboys.cn

Netty 全网最全示例源码地址猛戳-->https://github.com/Sniper2016/NettyStudy

前言:

最近在学习 netty,看了 Netty 权威指南,其中源码第一篇就讲到了 ByteBuffer 和 byteBuf 的区别,这里就总结一下。

ByteBuffer

bytebuffer 是 Java NIO 里面提供的字节容器。有一个指针用于处理读写操作,每次读写的时候都需要调用 flip()或是 clear()方法,不然将会报异常。

部分源码:

public abstract class ByteBuffer  extends Buffer{
}
public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
private int capacity;

}

成员变量关系

属性 描述
mark 调用 mark()方法的话,mark 值将存储当前 position 的值,等下次调用 reset()方法时,会设定 position 的值为之前的标记值
position 用来表示 bytes 的容量,那么可以想像 capacity 就等于 bytes.size(),此值在初始化 bytes 后,是不可变的
limit 用来表示 bytes 实际装了多少数据,可以容易想像得到 limit <= capacity,此值是可灵活变动的
capacity 用来表示在哪个位置开始往 bytes 写数据或是读数据,此值是可灵活变动的

他们之间的关系:mark <= position <= limit <= capacity

创建一个 bytebuffer

ByteBuffer bf = ByteBuffer.allocate(10);

position,limit 和 capacity 图解如下:

写入数据到 bytebuffer

bf.put((byte)’H’)
.put((byte)’e’)
.put((byte)’l’)
.put((byte)’l’)
.put((byte)’o’)

在操作 bytebuffer 的时候,每次往里面写入一个 byte,position 则会后移一位。

使用 flip() 刷新缓冲区为 读模式

bf.flip()

bytebuffer 有两种模式,分别是写模式和读模式,这两种模式通过使用 flip 方法进行模式。 如上将缓冲区切换为读模式,则 position 变成了初值位置 0,而 limit 变成了写模式下 position 位置。

读取数据

bf.get()

调用 get()获取缓冲区中的一个 byte。

清除缓冲区

bf.clear()

tips: 这个方法简单理解就是复位(Reset) 但不会清除数据(position=0, limit=capacity)

JDK 自带的 ByteBuffer 并不足够完美,它有以下缺陷:

摘抄自《Netty 权威指南》

  • ByteBuffer 长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的 POJO 对象大于 ByteBuffer 的容量时,会发生索引越界异常;
  • ByteBuffer 只有一个标识位控的指针 position,读写的时候需要手工调用 flip()和 rewind()等,使用者必须小心谨慎地处理这些 API,否则很容易导致程序处理失败;
  • ByteBuffer 的 API 功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。

ByteBuf

bytebuf 是 Netty 里的封装的数据缓存区,区别于 bytebuffer 里需要 position、limit、capacity 等属性来操作 bytebuffer 数据读写,而 bytebuf 里面则是通过 两个指针协助缓存区的读写操作,分别为 readIndex 和 writerIndex 。 在创建 bytebuf 的时候,readIndex 和 writerIndex 的值都是 0,但随着有数据被写入 writerIndex 会增加,读取数据的时候 readIndex 也会增加, 但是 readIndex 不会超过 writerIndex。

图解:

创建一个 bytebuf

ByteBuf bf= Unpooled.buffer(10,100)

write:写入 N 个字节之后 ByteBuf

read:读取 M 个字节后(M<N)

使用 demo:

ByteBuf bf= Unpooled.buffer(10,100);
bf.writeBoolean(true);
bf.writeInt(666);
bf.writeInt(777);
bf.writeInt(888);
System.out.println(bf.readBoolean());
System.out.println(bf.readInt());
System.out.println(bf.readInt());
System.out.println(bf);

ByteBuf 的基本分类:

AbstractByteBuf 之下有众多子类,大致可以从三个维度来进行分类,分别如下:

  • Pooled:池化内存,就是从预先分配好的内存空间中提取一段连续内存封装成一个ByteBuf 分给应用程序使用。
  • Unsafe:是JDK 底层的一个负责IO 操作的对象,可以直接拿到对象的内存地址,基于内存地址进行读写操作。
  • Direct:堆外内存,是直接调用JDK 的底层API 进行物理内存分配,不在JVM 的堆内存中,需要手动释放。

ByteBuf 最基本的读写API 操作在AbstractByteBuf 中已经实现了,其众多子类采用不同的策略来分配内存空间,下面对重要的几个子类总结如下:

  • PooledHeapByteBuf :池化的堆内缓冲区
  • PooledUnsafeHeapByteBuf :池化的Unsafe 堆内缓冲区
  • PooledDirectByteBuf :池化的直接(堆外)缓冲区
  • PooledUnsafeDirectByteBuf :池化的Unsafe 直接(堆外)缓冲区
  • UnpooledHeapByteBuf :非池化的堆内缓冲区
  • UnpooledUnsafeHeapByteBuf :非池化的Unsafe 堆内缓冲区
  • UnpooledDirectByteBuf :非池化的直接(堆外)缓冲区
  • UnpooledUnsafeDirectByteBuf :非池化的Unsafe 直接(堆外)缓冲区

ByteBuffer 和 ByteBuf 的区别

  • Netty 的 ByteBuf 采用了读写索引分离的策略(readerIndex 与 writerIndex),一个初始化(里面尚未有任何数据)的 ByteBuf 的 readerIndex 与 writerIndex 值都为 0
  • 当读索引与写索引处于同一个位置时,如果继续读取,那么就会抛出 IndexOutOfBoundsException。
  • ByteBuffer 只有一个标识位置的指针,读写的时候需要手动的调用 flip()和 rewind()等,否则很容易导致程序处理失败。而 ByteBuf 有两个标识位置的指针,一个写 writerIndex,一个读 readerIndex,读写的时候不需要调用额外的方法。
  • ByteBuffer 必须自己长度固定,一旦分配完成,它的容量不能动态扩展和收缩;ByteBuf 默认容器大小为 256,支持动态扩容,在允许的最大扩容范围内(Integer.MAX_VALUE)。
  • NIO 的 SocketChannel 进行网络读写时,操作的对象是 JDK 标准的 java.nio.byteBuffer。由于 Netty 使用统一的 ByteBuf 替代 JDK 原生的 java.nio.ByteBuffer,所以 ByteBuf 中定义了 ByteBuffer nioBuffer()方法将 ByteBuf 转换成 ByteBuffer。
正文到此结束