Netty中使用了ByteBuf作为数据容器,它相对于NIO的ByteBuffer做了不少改进。下面我们分别对它们进行介绍。

ByteBuffer

NIO为New IO, 区别于传统的Java IO。 Java Io 最核心的概念是流(stream),是面向流的编程。 java的流要么是输入流,要么是输出流,不可能同时包含两个。而java nio 有3个核心的概念。 selector, channel , buffer。 是面向块(block) 或者缓冲区(buffer)编程的。所有数据的读写都是通过Buffer来进行的。由于channel是双向的,更能反映出底层操作系统的真实情况,并且能支持异步读。buffer是个内存,底层实现是一个数组,数据的读或者写都是通过buffer来实现的。

ByteBuffer的UML图:

java BufferedReader 很慢 java io buffer_Netty

  1. HeapByteBuffer
    在jvm堆上面的一个buffer,底层的本质是一个数组
    由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收
  2. DirectByteBuffer
    底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据
    跟外设(IO设备)打交道时会快很多,因为外设读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy

0 <= mark <= position <= limit <= capacity
position: 位置,下一个要被读的元素的索引,每次读缓冲区数据时都会改变改值,为下次读写作准备。
mark: 为某一位置做标记,便于某些时候回退到该位置。
capacity: 初始化时候的容量。
limit: 当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

ByteBuffer API:

绝对方法:忽略Limit 与 position.
相对方法:limit 与position在操作中会被考虑到

  1. flip():
    是翻转(状态翻转,读/写切换) 。 将limit设置为当前的position, 将position设置为0
  2. clear():
    将limit设置为capcity, 将position设置为0
  3. compact():
    把从position到limit中的内容移到0到limit-position的区域内。将position设为最后一个未读元素的后面; 将Limit设置为capacity;现在buffer就准备好了,但是不会覆盖未读的数据。

ByteBuf

ByteBuf有两个指针:
0 <= readIndex <= writeIndex <= capcity

duplicate() 浅复制
copy() 深复制

注意: 通过索引来访问Byte时并不会改变真实的读索引和写索引;可以通过ByteBuff的readIndex()和writeIndex()来读和写索引。

Netty ByteBuf所提供的3种缓冲区类型:

  1. heap buffer.
    这是最常见的类型,ByteBuf将数据储存到JVM的堆空间中,并且将实际的数据放到byte buffer中。
    优点: 由于数据是储存在JVM的堆中,因此可以快速的创建和回收,并且它提供了直接访问内部字节数组的方法。
    缺点: 每次读取数据中,都需要先将数据复制到直接缓冲区再进行网络传输。
  2. direct buffer
    在堆之外直接分配内存空间,直接缓冲区并不会占用堆的容量空间,因为它是操作系统在本地内存进行数据分配。
    优点: 在使用Socket进行数据传送时,性能非常好。
    缺点:因为Direct Buffer是直接在操作系统内,分配与释放更加复杂,速度较慢。

Netty通过提供内存池来解决这个问题。直接缓冲区并不支持通过字节数组的方式访问数据、
重点: 对于后端的业务消息的编解码来说,推荐使用HeapByteBuf; 通过I/O通信线程在读写缓冲区时,推荐使用Direct Buffer.

  1. compostie buffer.
    (复合缓冲区,可放heap buffer,也可放direct buffer)

ByteBuffer和ByteBuf的差别

  1. Netty的ByteBuf采用了读写索引分离的策略(readIndex和writeIndex), 一个初始化(里面没有任何数据)的ByteBuf的readIndex与writeIndex都为0.
  2. 当读索引和写索引处于同一个位置时,我们继续读,会抛出indexoutboundsException.
  3. 对于ByteBuf的任何读写操作都会单独维护读写索引. maxCapcity最大容量的限制就是Integer.MAX_VLAUE.

其中,JDK的ByteBuffer缺点:

  1. final byte[]hb; 这是存储对象的对象声明;为final,不能动态扩容与缩容;存储数据很大时很可能出现indexoutboundsException。预防这个异常需要知道存储前知道大小。如果ByteBuffer空间不足:我们只有一个解决办法,创建一个ByteBuffer对象,然后将之前的ByteBuffer复制过去,这些需要使用者自己去做,很不方便。
  2. ByteBuffer只用一个position,进行读写切换时需要调用flip或rewind,不方便。