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所示。