Buffer
一种用于存储特定原始类型数据的容器。
缓冲区是具有特定原始类型元素的线性、有限序列。除了其内容外,缓冲区的基本属性包括其容量(capacity)、限制(limit)和位置(position):
- 缓冲区的容量是指它所能包含的元素数量。缓冲区的容量永远不会是负数,并且在设置之后永远不会改变。
- 缓冲区的限制是指第一个不应该被读取或写的元素的索引。缓冲区的限制不会是负数,并且永远不会大于它的容量(capacity)。
- 缓冲区的位置是指下一个要被读取或写的元素的索引。缓冲区的位置不会是负数,并且永远不会大于它的限制(limit)。
对于每种非布尔的原始类型此类都有一个对应的子类。
传输数据
这个类的每个子类定义了两类 get 和 put 操作:
- 相对操作从当前位置开始读取或写入一个或多个元素,然后按传输的元素数量增加位置。如果请求超出了限制,那么相对 get 操作会抛出
BufferUnderflowException
异常,相对 put 操作会抛出BufferOverflowException
异常;在任一情况下,都不会传输数据。 - 绝对操作需要一个明确的元素索引,并且不影响位置。如果索引参数超出限制,绝对 get 和 put 操作会抛出
IndexOutOfBoundsException
异常。
数据当然也可以通过对应通道的输入/输出操作转移到缓冲区中或从缓冲区中转出,这些操作始终是相对于当前位置的。
标记和重置
缓冲区的标记(mark)是当调用reset
方法时其位置将被重置到的索引。标记(mark)并不会总是被定义,但当它被定义时,它永远不会是负数,并且永远不会大于位置(position)。如果已定义标记,那么当位置(position)或限制(limit)被调整到一个比标记小的值时,标记将被丢弃。如果标记未定义,那么调用reset
方法将导致抛出InvalidMarkException
异常。
不变式
以下不变量适用于标记、位置、限制和容量值:
0 <= mark <= position <= limit <= capacity
新创建的缓冲区总是位置(position)为零,并且标记(mark)是未定义的。初始的限制(limit)可能是零,或者它可能是取决于缓冲区的类型以及构建方式的其他值。新分配的缓冲区的每个元素都被初始化为零。
清除、翻转、倒带
除了访问位置、限制和容量值以及标记和重置的方法外,此类还定义了对缓冲器的以下操作:
clear
使缓冲区准备好进行新的一系列通道读取或相对 put 操作序列:它将限制(limit)设置为容量(capacity),将位置(position)设置为零。flip
使缓冲区准备好进行新的一系列通道写入或相对 get 操作序列:它将限制(limit)设置为当前位置(position),然后将位置(position)设置为零。rewind
使缓冲区准备好重新读取它已经包含的数据:它保持限制不变,并将位置设置为零。
只读缓冲区
每个缓冲区都是可读的,但不是每个缓冲区都是可写的。每个缓冲区类的变异方法被指定为可选操作,当在只读缓冲区上调用这些方法时,会抛出ReadOnlyBufferException
异常。只读缓冲区不允许更改其内容,但其标记、位置和限制值是可变的。可以通过调用其isReadOnly
方法来确定缓冲区是否为只读。
线程安全性
多个并发线程使用同一个缓冲区是不安全的。如果一个缓冲区要由多个线程使用,那么应该通过适当的同步来控制对该缓冲区的访问。
调用链
这个类中没有其他返回值要返回的方法被指定为返回它们被调用的缓冲区。这样可以将方法调用链接在一起;例如,语句:
b.flip();
b.position(23);
b.limit(42);
可以用一个更紧凑的语句代替:
b. flip().position(23).limit(42);
在缓冲区中维护的元素进行遍历和分割的拆分器的特性。
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
标记,不变式:mark <= position <= limit <= capacity
位置。
限制。
容量。
仅由直接缓冲区使用。 注意:为了提高 JNI 中 GetDirectBufferAddress 的速度,这里提升了它。
在检查不变式之后,创建一个具有给定标记、位置、限制和容量的新缓冲区。
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
返回此缓冲区的容量。
public final int capacity() {
return capacity;
}
返回此缓冲区的位置。
public final int position() {
return position;
}
设置此缓冲区的位置。如果标记已定义且大于新位置,则会将其丢弃。
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
返回此缓冲区的限制。
public final int limit() {
return limit;
}
设置此缓冲区的限制。如果位置大于新限制,则将其设置为新限制。如果标记被定义并且大于新的限制,那么它将被丢弃。
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > newLimit) position = newLimit;
if (mark > newLimit) mark = -1;
return this;
}
将此缓冲区的标记设置在其位置。
public final Buffer mark() {
mark = position;
return this;
}
将此缓冲区的位置重置为先前标记的位置。
调用此方法既不会更改也不会丢弃标记的值。
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
清除此缓冲区。位置设置为零,限制设置为容量,标记被丢弃。
在使用一系列通道读取或放置操作来填充此缓冲区之前,请调用此方法。例如:
buf.clear(); // 准备用于读取的缓冲区
in.read(buf); // 读取数据
这种方法实际上并不会擦除缓冲区中的数据,但它被命名为擦除,因为它最常用于这种情况。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
翻转此缓冲区。将限制设置为当前位置,然后将位置设置为零。如果定义了标记,则会将其丢弃。
在一系列通道读取或放置操作之后,调用此方法为一系列通道写入或相对获取操作做准备。例如:
buf.put(magic); // 准备标头
in.read(buf); // 将数据读取到缓冲区的其余部分
buf.flip(); // 翻转缓冲区
out.write(buf); // 将标头+数据写入通道
当数据从一个地方传输到另一个地方时,此方法经常与compact
方法结合使用。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
回退此缓冲区。位置被设置为零,标记被丢弃。
在一系列通道写入或获取操作之前调用此方法,假设已经适当设置了限制。例如:
out.write(buf); // 写入剩余数据
buf.rewind(); // 回退缓冲区
buf.get(array); // 复制数据进数组
返回当前位置和限制之间的元素数。
public final int remaining() {
return limit - position;
}
返回当前位置和限制之间是否有任何元素。
public final boolean hasRemaining() {
return position < limit;
}
返回当前缓冲区是否是只读的。
判断这个缓冲区是否基于一个可访问的数组。
如果此方法返回true
,则可以安全地调用array
和arrayOffset
方法。
返回支持此缓冲区的数组(可选操作)。
此方法旨在使基于数组的缓冲区能够更有效地传递给原生代码。具体的子类为这个方法提供了更强类型的返回值。
对此缓冲区内容的修改将导致返回数组的内容被修改,反之亦然。
在调用此方法之前先调用hasArray
方法,以确保此缓冲区具有可访问的后备数组。
返回缓冲区第一个元素的缓冲区后备数组中的偏移量(可选操作)。
如果此缓冲区是基于数组的,则缓冲区位置p对应于数组索引p + arrayOffset()。
在调用此方法之前先调用hasArray
方法,以确保此缓冲区具有可访问的后备数组。
返回这个缓冲区是否是直接缓冲区。
当且仅返回true
时此缓冲区是直接缓冲区。
根据限制检查当前位置,如果当前位置不小于限制,则抛出BufferUnderflowException
,否则递增位置。
返回其递增之前的当前位置值。
final int nextGetIndex() { // package-private
int p = position;
if (p >= limit)
throw new BufferUnderflowException();
position = p + 1;
return p;
}
和上面的nextGetIndex
方法类似,只不过上面的方法是尝试给位置+1,而此方法尝试给当前位置加指定的数量(参数nb
)。
final int nextGetIndex(int nb) { // package-private
int p = position;
if (limit - p < nb)
throw new BufferUnderflowException();
position = p + nb;
return p;
}
根据限制检查当前位置,如果不小于限制,则抛出BufferOverflowException
,否则递增位置。
返回其递增之前的当前位置值。
final int nextPutIndex() { // package-private
int p = position;
if (p >= limit)
throw new BufferOverflowException();
position = p + 1;
return p;
}
和上面的nextPutIndex
方法类似,只不过上面的方法是尝试给位置+1,而此方法尝试给当前位置加指定的数量(参数nb
)。
final int nextPutIndex(int nb) { // package-private
int p = position;
if (limit - p < nb)
throw new BufferOverflowException();
position = p + nb;
return p;
}
根据限制检查给定的索引,如果该索引不小于限制或小于零,则抛出IndexOutOfBoundsException
。
final int checkIndex(int i) { // package-private
if ((i < 0) || (i >= limit))
throw new IndexOutOfBoundsException();
return i;
}
检查索引是否还能增加nb
。
final int checkIndex(int i, int nb) { // package-private
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
}
返回标记。
final int markValue() { // package-private
return mark;
}
截断。
final void truncate() { // package-private
mark = -1;
position = 0;
limit = 0;
capacity = 0;
}
放弃标记。
final void discardMark() { // package-private
mark = -1;
}
检查边界。
static void checkBounds(int off, int len, int size) { // package-private
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}