Buffer的重要field

buffer是线性、有限、指定类型的数据缓冲集合,在nio的定义中,buffer的关键属性:

  • capacity:最多能承载的元素数,永远非负且不会改变
  • limit首个不能被读写的元素的index,永远非负且不会大于capacity,因为下标从0开始,limit=capacity时表示整个buffer可用
  • position下一个应当被读写的元素的index,永远非负且不会大于limit
  • mark:用于reset的标记,当mark未被定义时,它是-1;当它被定义时,它永远非负且不会大于position

所以,我们有了以下的约束:0<=mark<=position<=limit<=capacity

ByteBuffer为例的读写操作

先研究读写操作对上述field的操作和要求,可以更好的理解之后部分对于Buffer给出的一些操作方法的意义。
对ByteBuffer的读写操作,最终会落到其声明为abstract的单个读写操作get()和put()上。

get()

ByteBuffer对它的约定是:读取position处的一个字节,并increment作为index的position
以HeapByteBuffer为例,它的实体是分配在堆上的字节数组,其get实现方法为:

  1. public byte get() {
  2. return hb[ix(nextGetIndex())];
  3. }

ix添加offset偏移,nextGetIndex()则检查后increment position,同时返回旧position。

put()

ByteBuffer对它的约定与get()类似,将一个字节写入position并increment之,实现省略。

HeapByteBuffer和DirectByteBuffer的区别

从实现上可以看到,hbb的操作对象是内存中的字节数组,而dbb则利用Unsafe提供的接口直接根据address读写内存,查阅资料可以得到,这个直接内存其实也是java进程malloc的内存,但相对于Java程序是堆外内存,可由jvm参数配置,不参与minorGC,同时也可以在配置的情况下不参与fullGC,总之是块法外之地,hbb向channel写数据时,首先需要将数据拷贝到DirectBuffer,这就意味着直接申请dbb可以省略一次拷贝,所以一般设计channel读写的buffer都申请为dbb。
XX:MaxDirectMemorySize参数可以配置直接内存大小,这块内存应由程序员悉心管理。

clear、flip、rewind

这三个方法在Buffer类中就已经被定义并声明为final,这些操作定义了基于上述field控制的buffer读写操纵手法。

clear

  1. public final Buffer clear() {
  2. position = 0;
  3. limit = capacity;
  4. mark = -1;
  5. return this;
  6. }

重置position到0,把limit设到最大,清除mark,相当与清除缓冲区的状态,invalid数据,并使得整个缓冲区可用。一般用于向buffer写入数据的准备。

filp

  1. public final Buffer flip() {
  2. limit = position;
  3. position = 0;
  4. mark = -1;
  5. return this;
  6. }

清除mark并使position和limit夹紧已读取的数据部分,该操作用于“翻转”读写操作,在一系列的读操作之后,通过filp()的调用使得从buffer读取操作准备就绪(写完了要读)。

rewind

  1. public final Buffer rewind() {
  2. position = 0;
  3. mark = -1;
  4. return this;
  5. }

清除mark并重置position,它用于一系列从buffer中读操作之后,其作用是重新读取上一波中已读取的数据

mark和reset

这个机制使得操作Buffer时,能够提供比rewind更灵活的机制,用于在读写过程中提供回溯的机制。

  1. public final Buffer mark() {
  2. mark = position;
  3. return this;
  4. }
  5. public final Buffer reset() {
  6. int m = mark;
  7. if (m < 0)
  8. throw new InvalidMarkException();
  9. position = m;// 这里引入m似乎是为了线程安全考虑?但Buffer并非是线程安全的结构
  10. return this;
  11. }

它在缓冲区中留下一个标记,可以提供读和写的回溯。

线程安全

显然,nio提供的Buffer并没有提供原子操作,因此不是线程安全的

存在的问题

Java nio提供的Buffer仍然存在一系列问题,如不能读写分离:filp只能支持由写模式转换为读模式,而在读过程中却无法插入写操作,否则就会覆盖需要读的内容,因此,netty重写了用于channel读写的buffer格式。