概念:
在NIO中所有的数据都是用缓冲区处理的,任何时候访问NIO中的数据都是通过缓冲区操作。它本质上是一个数组,比如我们最常使用的Byte Buffer其实就是一个Byte数组。它还可以使除了boolean外很多其它数组类型,但是它不完全仅仅是一个数组,缓冲区还对数据提供了结构化访问以及维护读写位置等信息。
关键属性:
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
/**
* Returns the number of elements between the current position and the
* limit.
*
* @return The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}
-
Capacity:
能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。当buffer写满时,需要先清空才能继续写入。 -
Limit:
是buffer中不可以被读或者写的第一个元素的位置,limit的大小永远不会超过capacity(在写模式下,limit等于capacity) -
Position:
写模式下,position从0开始,每写入一个单位的数据,position前进一位,position最大可到达(capacity-1)的位置。当Buffer从写模式切换为读模式时,position将重置为0。读取数据时,同样地,position每读取一个单位,前进一位,此时,position最大可到达limit的位置(实际最大可读取的位置是(limit-1))。 -
标记(Mark):
一个备忘位置。调用mark( )来设定mark = postion。调用reset( )设定position = mark。标记在设定前是未定义的(undefined)。 -
remaining:
这个缓冲区中剩余元素的数量
基础API用法:
先创建一个容量为16的intBuffer看一下
IntBuffer intBuffer = IntBuffer.allocate(16);
IntStream.rangeClosed(1,10).forEach(intBuffer::put);
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
进入到源代码我们可以看到底层实现确实是数组,debug中也可以清晰的看到此时创建了16个数组
put():
下面我们往里面填充几个数值看看参数都有什么变化:
在注入10个数值后,数组对应坐标都有了值,同时position指针也移动到了目前数组10的位置
flip():
翻转方法是最常使用的一个方法,我们可以看到调用此方法后position重置到了0回到了数组的初始位置,而limit指向了之前position位置。
源码中给了详细的解释,逻辑也十分简单,主要是对position指针复原到数组头,limit指向数组当前实际存储的最后位置,方便进行后续的写操作等
/**
* Flips this buffer. The limit is set to the current position and then
* the position is set to zero. If the mark is defined then it is
* discarded.
*
* <p> After a sequence of channel-read or <i>put</i> operations, invoke
* this method to prepare for a sequence of channel-write or relative
* <i>get</i> operations. For example:
*
* <blockquote><pre>
* buf.put(magic); // Prepend header
* in.read(buf); // Read data into rest of buffer
* buf.flip(); // Flip buffer
* out.write(buf); // Write header + data to channel</pre></blockquote>
*
* <p> This method is often used in conjunction with the {@link
* java.nio.ByteBuffer#compact compact} method when transferring data from
* one place to another. </p>
*
* @return This buffer
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
hasRemaining():
判断当前缓存中是否还有值,每读取一个值position就会往后移动一位,知道最后position=limit取出所有值退出循环
public final boolean hasRemaining() {
return position < limit;
}
compact
这个方法起初看了很多解释都不太理解云里雾里的感觉,自己打一遍断点就清晰很多。它的作用就是将当前position前的已读数据清除,再将position到limit之间的未读数据复制到数组头部(注意遗留下来的数据仍然存在,之后加入新值时会被覆盖掉)。position重置到limit减去当前position的位置。即移动之后未读值的位置,当有新值加入时将从此位置进行插入。看例子就明白了语言有点不太直观
public IntBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
discardMark();
return this;
}
根据这段代码打断点来看一下,我手动将position设置为3模拟当前读取到了3这种场景
IntBuffer intBuffer = IntBuffer.allocate(16);
IntStream.rangeClosed(1,10).forEach(intBuffer::put);
intBuffer.flip();
intBuffer.position(3);
intBuffer.compact();
intBuffer.put(11);
intBuffer.put(12);
执行compact方法后position指针指向了7的位置,之前的旧值仍然存在。我们继续添加新值就会将此覆盖掉,从而达到资源的最大利用。到这里应该就恍然大悟了吧。clear()方法只是把指针移到位置0,并没有真正清空数据。而compact会清楚position之前的已读数据。
slice():
缓冲区切片,将一个大缓冲区的一部分切出来,作为一个单独的缓冲区,但是它们公用同一个内部数组。切片从原缓冲区的position位置开始,至limit为止。原缓冲区和切片各自拥有自己的属性。