NIO的Buffer缓冲区是一个封装好的数据数组类型。它将数据数组封装在一个对象中,并定义了一组用于处理缓冲区中数据数组的API。因此避免了在开发中直接与一个简单数组类型进行编程交互。
缓冲器属性
Buffer缓冲区内部定义了一些重要的属性来描述缓冲区的结构和当前状态,我们先来熟悉Buffer缓冲区类型的结构,所有的Buffer缓冲区类型都具有以下四个重要的属性:
- Capacity(容量)
Capacity是缓冲区能够容纳数据元素的最大容量。该容量在Buffer缓冲区对象创建时被指定,并且在此之后不能被改变。
- Position(位置)
Position指向Buffer缓冲区下一个即将被读取或写入数据的索引。当调用Buffer缓冲区相应的API可能会改变Position的状态,即Position可以在缓冲区中前后移动,随时指向不同的索引。缓冲区的Position位置永远不会为负数,也永远不会大于其Limit。
- Limit(上界)
Limit指向Buffer缓冲区下一个即将不能读取或写入的元素的索引。 缓冲区的Limit值永远不会为负数,也永远不会大于其容量。Buffer对象被创建时,Limit的初始值与Capacity的值相等。
Buffer缓冲区类型提供了很多API会使缓冲区对象的Limit状态值发生改变,而Limit才是缓冲区中读数据、写数据的边界,而Capacity可以认为是缓冲区中理论上最大的读、写数据边界。记住这个就能理解Buffer缓冲区内部这4个属性发生改变的技术细节和原因。
- Mark(标记)
Mark用于保存当前Position所在的位置。通过调用缓冲器的 mark( ) 函数使得 Mark = Position,Mark就像是一个保存当前位置索引状态的备忘录,在Position因为其他操作发生改变后,能够通过 reset() 操作重新回到标记所记录的位置。然而在实际编程的时候也经常会使用一个外部的变量来记录当前位置,之后通过变量的记录重新回到之前的位置。
图解缓冲区
我们创建一个容量为12的 ByteBuffer 对象。为了便于理解缓冲区对象,通过如 图1 所示的逻辑视图来直观的体会 ByteBuffer 对象的内部属性之间的关系。
图1 ByteBuffer.allocate(12) 初始化
ByteBuffer的四个属性:Capacity被初始化为12,它是缓冲区对象的容量。Position被初始化为0,它指向缓冲区中索引为0的数据槽,意味着它是下一个准备读取或准备写入的数据槽索引。Limit初始化时等于Capacity的值,即缓冲器不能继续读取或写入数据的边界。Mark属性初始化时是未定义的(即 -1),表示没有记录任何可以回到上一次位置的历史状态。
Capacity属性是固定的,而另外的三个属性会受到缓冲器的读取,写入,翻转,重置,压缩等操作而发生改变,它们就像游标一样在缓冲区内前后移动。
Buffer类型提供了如下API来访问、修改缓冲器的属性,但无论缓冲区的属性如何被改变,Buffer缓冲区中这四个属性值总是遵循以下约束关系:
0 <= mark <= position <= limit <= capacity
属性相关API
public Buffer mark()
将缓冲区中的当前位置保存在mark状态中,用于之后position改变后还能回到之前的位置。
public Buffer mark() {
mark = position;
return this;
}
public final int capacity( )
返回当前Buffer缓冲区的Capacity值。即Buffer缓冲区能够容纳数据元素的最大容量。
public final int capacity() {
return capacity;
}
public final int position( )
返回当前Buffer缓冲区的Position位置值。即Buffer缓冲区中下一个将要被读取或写入的数据索引位置。
public final int position() {
return position;
}
public final Buffer position(int newPosition)
将当前Buffer缓冲区的Position立即移动到一个新的位置,该方法会对newPosition做一些必要的检查:
- 新位置newPosition如果为一个负数,将导致向下越界。方法将会抛出IllegalArgumentException异常。
- 新位置newPosition如果大于当前 Limit 所指向的位置,也就是说newPosition一下就越过当前的读取、写入数据的边界。方法同样会抛出IllegalArgumentException异常。
- 新位置newPosition如果小于mark所指向的位置,Mark将其重置为-1。Mark起到标记当前Position位置的作用,因此它最开始会与Position相等,随着Position位置的向前移动,Mark将越来越小于Position所指向的位置。因此Mark的位置始终不会出现越过Position所在位置的状况,如果newPosition的值导致Mark违背了这个约束,则Mark需要被丢弃,即设置为 -1。
public Buffer position(int newPosition) {
if (newPosition > limit | newPosition < 0)
throw createPositionException(newPosition);
if (mark > newPosition) mark = -1;
position = newPosition;
return this;
}
public final int limit( )
返回当前Buffer缓冲区的Limit所指向的索引位置。记住Limit 才是缓存区读取、写入操作的有效边界,而Capacity是缓冲区的容器的最大边界。
public final int limit() {
return limit;
}
public final Buffer limit (int newLimit)
将当前Buffer缓冲区的Limit指向一个新的位置,该方法会对newLimit做一些必要的检查:
- 新的限制边界newLimit如果为负数,将导致向下越界。方法将会抛出IllegalArgumentException异常。
- 新的限制边界newLimit如果大于缓冲区的最大容量Capacity值,将导致向上越界。方法同样会抛出IllegalArgumentException异常。
- 新的限制边界newLimit如果小于Position,即newLimit所指向的边界限制一下跑到了Position的后面,就不能形成一个从Position到Limit之间的有效区间。那这时要将Position向后移动到与newLimit相同的位置,既让Position和Limit两个位置的索引重合在一起,避免了违反缓冲区属性之间的约束关系。但是此刻即无法读取数据,也无法写入数据了,因为Position已经到达了Limit限制读、写的边界。
- 更新了Limit位置的时候,同时也要检测对Mark属性的影响,如果 Mark > newLimit,即Mark的位置越过了Limit的位置,因此Mark的值违背了Buffer缓冲区属性之间的约束,将会丢弃Mark当前所指向的索引,将其重置为 -1。Limit 函数的实现如下代码所示。
public Buffer limit(int newLimit) {
if (newLimit > capacity | newLimit < 0)
throw createLimitException(newLimit);
limit = newLimit;
if (position > newLimit) position = newLimit;
if (mark > newLimit) mark = -1;
return this;
}
public final int remaining( )
返回当前Position位置与Limit限制之间的元素数,即缓冲区中还能继续写入或读取的元素数量。remaining( )函数的实现如下代码所示。
public final int remaining() {
int rem = limit - position;
return rem > 0 ? rem : 0;
}
通过如下所示的逻辑视图来更加直观的理解缓冲区对象中Position属性与Limit属性对remaining的影响。
图 remaining属性示例1
图 remaining属性示例2
public final boolean hasRemaining( )
返回缓冲区对象当前Position 位置属性与 Limit 限制属性之间是否存在可以被读取或写入的数据空间。hasRemaining( )函数的实现如下代码所示。
public final boolean hasRemaining() {
return position < limit;
}