Buffer的工作方式

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

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

    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()方法源码可知,执行的是以下代码

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()方法,查看源码得知

 public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
    }

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

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

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),

 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 @ 2015-08-10 21:48  吴小雨  阅读(1060)  评论(0编辑  收藏  举报