Buffer的工作方式

1、Buffer的工作方式
  前面《java NIO的工作方式》介绍了Selector检测到通信信道I/O有数据传输时,通过select()方法取得SocketChannel,将数据读取或写入Buffer缓冲区,下面讨论Buffer如何接受和写出数据。通过查看JDK源码可知道,Buffer的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
Buffer(int mark, int pos, int lim, int cap) {  // package-private
   if (cap < 0)
       throw new IllegalArgumentException();
   this.capacity = cap;
   limit(lim);
   position(pos);
   if (mark >= 0) {
       if (mark > pos)
       throw new IllegalArgumentException();
       this.mark = mark;
   }
   }

  Buffer可以简单的理解为一组基本数据类型的元素列表,它通过几个变量来保存这个数据的当前位置状态,也就是四个索引

1
2
3
4
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

表1-1 Buffer中的索引及说明

在实际操作数据时他们关系如图1-2所示。

我们通过ByteBuffer.allocate(11)方法创建一个11个byte的数组缓冲区,初始状态如图1-2所示,position的位置为0,capacity和limit默认都是数组长度。当我们写入5个字节时位置变化如图1-3所示。

注意:我们查看ByteBuffer.allocate()方法源码可知,执行的是以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
         byte[] hb, int offset)
    {
    super(mark, pos, lim, cap);
    this.hb = hb;
    this.offset = offset;
    }
 
 public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
    }

返回的是HeapByteBuffer又是ByteBuffer的子类,并且在HeapByteBuffer的构造方法中执行的是这样一个语句:

super(-1, 0, lim, cap, new byte[cap], 0);

也就是说调用的还是ByteBuffer中的构造方法,包范围内使用。这个方法做了如下工作,首先调用Buffer的构 造方法,依次初始化mark、position、limit、capacity,然后初始化ByteBuffer的属性byte数组,接着初始 offset,这样使用allocate方法就可以构造出一个ByteBuffer对象了。


这时我们需要将缓冲区的5个字节数据写入Channel通信信道,所以我们调用byteBuffer.flip()方法,查看源码得知

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

limit走到position的位置,而position回到了初始位置0,倒转了缓冲区,数组的状态发生如图1-4所示的变化。

这时底层操作系统就可以从缓冲区中正确读取这5个字节数据并发送出去了。在下一次写数据之前我们在调一下clear()方法,缓冲区的索引状态又回到初始位置。

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

  这里还要说明一下mark,当我们调用mark()方法时,它将记录当前position的前一个位置,当我们调用reset时,position将恢复mark记录下来的值。还有一点需要说明,通过Channel获取I/O数据首先要经过操作系统的Socket缓冲区再将数据复制到Buffer中,这个操作系统缓冲区就是底层的TCP协议关联的RecvQ或SendQ队列,从操作系统缓冲区到用户缓冲区复制数据比较耗性能,Buffer提供了另外一种直接操作操作系统缓冲区的方式,即ByteBuffer.allocateDirector(size),

1
2
3
public static ByteBuffer allocateDirect(int capacity) {
       return new DirectByteBuffer(capacity);
   }

这个方法返回的DirectorByteBuffer就是与底层存储空间关联的缓冲区,它通过Native代码操作非JVM堆的内存空间。每次创建或者释放的时候都调用一次System.gc()。注意,在使用DirectorByteBuffer时可能会引起JVM内存泄露问题。DirectByteBuffer和Non-Direct Buffer(HeapByteBuffer)对比如图1-5所示。

posted @   吴小雨  阅读(1070)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示