Buffer 类是 java.nio 的构造基础。一个 Buffer 对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。缓冲区可以被写满或释放。对于每个非布尔原始数据类型都有一个缓冲区类,即 Buffer 的子类有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,是没有 BooleanBuffer 之说的。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节。非字节缓冲区可以在后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。
◇ 缓冲区的四个属性 所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息,这四个属性尽管简单,但其至关重要,需熟记于心:- 容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
- 上界(Limit):缓冲区的第一个不能被读或写的元素。缓冲创建时,limit 的值等于 capacity 的值。假设 capacity = 1024,我们在程序中设置了 limit = 512,说明,Buffer 的容量为 1024,但是从 512 之后既不能读也不能写,因此可以理解成,Buffer 的实际可用大小为 512。
- 位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get() 和 put() 函数更新。
- 标记(Mark):一个备忘位置。标记在设定前是未定义的(undefined)。使用场景是,假设缓冲区中有 10 个元素,position 目前的位置为 2,现在只想发送 6 - 10 之间的缓冲数据,此时我们可以 buffer.mark(buffer.position()),即把当前的 position 记入 mark 中,然后 buffer.postion(6),此时发送给 channel 的数据就是 6 - 10 的数据。发送完后,我们可以调用 buffer.reset() 使得 position = mark,因此这里的 mark 只是用于临时记录一下位置用的。
请切记,在使用 Buffer 时,我们实际操作的就是这四个属性的值。我们发现,Buffer 类并没有包括 get() 或 put() 函数。但是,每一个Buffer 的子类都有这两个函数,但它们所采用的参数类型,以及它们返回的数据类型,对每个子类来说都是唯一的,所以它们不能在顶层 Buffer 类中被抽象地声明。它们的定义必须被特定类型的子类所遵从。若不加特殊说明,我们在下面讨论的一些内容,都是以 ByteBuffer 为例,当然,它当然有 get() 和 put() 方法了。
◇ 相对存取和绝对存取Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- // This is a partial API listing
- public abstract byte get( );
- public abstract byte get (int index);
- public abstract ByteBuffer put (byte b);
- public abstract ByteBuffer put (int index, byte b);
- }
Java代码
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
Java代码
- buffer.limit(buffer.position()).position(0);
Java代码
- for (int i = 0; buffer.hasRemaining(); i++) {
- myByteArray[i] = buffer.get();
- }
Java代码
- int count = buffer.hasRemaining();
- for (int i = 0; i < count; i++) {
- myByteArray[i] = buffer.get();
- }
- 数据元素 2 - 5 被复制到 0 - 3 位置,位置 4 和 5 不受影响,但现在正在或已经超出了当前位置,因此是“死的”。它们可以被之后的 put() 调用重写。
- Position 已经被设为被复制的数据元素的数目,也就是说,缓冲区现在被定位在缓冲区中最后一个“存活”元素的后一个位置。
- 上界属性被设置为容量的值,因此缓冲区可以被再次填满。
- 调用 compact() 的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。该例子中,你当然可以将 Me 之前已经读过,即已经被释放过。
- 两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且buffer 绝不会等于非 buffer对象。
- 两个对象都剩余同样数量(limit - position)的元素。Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。
- 在每个缓冲区中应被 get() 函数返回的剩余数据元素序列([position, limit - 1] 位置对应的元素序列)必须一致。
两个被认为是相等的缓冲区 两个被认为是不相等的缓冲区 缓冲区也支持用 compareTo() 函数以词典顺序进行比较,当然,这是所有的缓冲区实现了 java.lang.Comparable 语义化的接口。这也意味着缓冲区数组可以通过调用 java.util.Arrays.sort() 函数按照它们的内容进行排序。 与 equals() 相似,compareTo() 不允许不同对象间进行比较。但 compareTo()更为严格:如果你传递一个类型错误的对象,它会抛出 ClassCastException 异常,但 equals() 只会返回 false。 比较是针对每个缓冲区你剩余数据(从 position 到 limit)进行的,与它们在 equals() 中的方式相同,直到不相等的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。这里有个顺序问题:下面小于零的结果(表达式的值为 true)的含义是 buffer2 < buffer1。切记,这代表的并不是 buffer1 < buffer2。
Java代码
- if (buffer1.compareTo(buffer2) < 0) {
- // do sth, it means buffer2 < buffer1,not buffer1 < buffer2
- doSth();
- }
Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- public ByteBuffer get(byte[] dst);
- public ByteBuffer get(byte[] dst, int offset, int length);
- public final ByteBuffer put(byte[] src);
- public ByteBuffer put(byte[] src, int offset, int length);
- }
- 如果你所要求的数量的数据不能被传送,那么不会有数据被传递,缓冲区的状态保持不变,同时抛出BufferUnderflowException异常。
- 如果缓冲区中的数据不够完全填满数组,你会得到一个异常。这意味着如果你想将一个小型缓冲区传入一个大型数组,你需要明确地指定缓冲区中剩余的数据长度。
如果缓冲区存有比数组能容纳的数量更多的数据,你可以重复利用如下代码进行读取:
Java代码
- byte[] smallArray = new Byte[10];
- while (buffer.hasRemaining()) {
- int length = Math.min(buffer.remaining(), smallArray.length);
- buffer.get(smallArray, 0, length);
- // 每取出一部分数据后,即调用 processData 方法,length 表示实际上取到了多少字节的数据
- processData(smallArray, length);
- }
Java代码
- public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
- // This is a partial API listing
- public static CharBuffer allocate (int capacity);
- public static CharBuffer wrap (char [] array);
- public static CharBuffer wrap (char [] array, int offset, int length);
- public final boolean hasArray();
- public final char [] array();
- public final int arrayOffset();
- }
Java代码
- // 这段代码隐含地从堆空间中分配了一个 char 型数组作为备份存储器来储存 100 个 char 变量。
- CharBuffer charBuffer = CharBuffer.allocate (100);
- /**
- * 这段代码构造了一个新的缓冲区对象,但数据元素会存在于数组中。这意味着通过调用 put() 函数造成的对缓
- * 冲区的改动会直接影响这个数组,而且对这个数组的任何改动也会对这个缓冲区对象可见。
- */
- char [] myArray = new char [100];
- CharBuffer charbuffer = CharBuffer.wrap (myArray);
- /**
- * 带有 offset 和 length 作为参数的 wrap() 函数版本则会构造一个按照你提供的 offset 和 length 参
- * 数值初始化 position 和 limit 的缓冲区。
- *
- * 这个函数并不像你可能认为的那样,创建了一个只占用了一个数组子集的缓冲区。这个缓冲区可以存取这个数组
- * 的全部范围;offset 和 length 参数只是设置了初始的状态。调用 clear() 函数,然后对其进行填充,
- * 直到超过 limit,这将会重写数组中的所有元素。
- *
- * slice() 函数可以提供一个只占用备份数组一部分的缓冲区。
- *
- * 下面的代码创建了一个 position 值为 12,limit 值为 54,容量为 myArray.length 的缓冲区。
- */
- CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42);
Java代码
- public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
- // This is a partial API listing
- public abstract CharBuffer duplicate();
- public abstract CharBuffer asReadOnlyBuffer();
- public abstract CharBuffer slice();
- }
Java代码
- CharBuffer buffer = CharBuffer.allocate(8);
- buffer.position(3).limit(6).mark().position (5);
- CharBuffer dupeBuffer = buffer.duplicate();
- buffer.clear();
Java代码
- CharBuffer buffer = CharBuffer.allocate(8);
- buffer.position(3).limit(5);
- CharBuffer sliceBuffer = buffer.slice();
Java代码
- package java.nio;
- public final class ByteOrder {
- public static final ByteOrder BIG_ENDIAN;
- public static final ByteOrder LITTLE_ENDIAN;
- public static ByteOrder nativeOrder();
- public String toString();
- }
Java代码
- public abstract class CharBuffer extends Buffer implements Comparable, CharSequence {
- // This is a partial API listing
- public final ByteOrder order();
- }
Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- // This is a partial API listing
- public final ByteOrder order();
- public final ByteBuffer order(ByteOrder bo);
- }
- 创建一个临时的直接 ByteBuffer 对象。
- 将非直接缓冲区的内容复制到临时直接缓冲区中。
- 使用临时直接缓冲区执行低层 I/O 操作。
- 临时直接缓冲区对象离开作用域,并最终成为被回收的无用数据。
这可能导致缓冲区在每个 I/O 上复制并产生大量对象,而这种事都是我们极力避免的。如果你仅仅为一次使用而创建了一个缓冲区,区别并不是很明显。另一方面,如果你将在一段高性能脚本中重复使用缓冲区,分配直接缓冲区并重新使用它们会使你游刃有余。
直接缓冲区可能比创建非直接缓冲区要花费更高的成本,它使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准 JVM 堆栈,不受垃圾回收支配,因为它们位于标准 JVM 堆栈之外。 直接 ByteBuffer 是通过调用具有所需容量的 ByteBuffer.allocateDirect() 函数产生的。注意,wrap() 函数所创建的被包装的缓冲区总是非直接的。与直接缓冲区相关的 api:Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- // This is a partial API listing
- public static ByteBuffer allocateDirect (int capacity);
- public abstract boolean isDirect();
- }
Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- // This is a partial API listing
- public abstract CharBuffer asCharBuffer();
- public abstract ShortBuffer asShortBuffer( );
- public abstract IntBuffer asIntBuffer( );
- public abstract LongBuffer asLongBuffer( );
- public abstract FloatBuffer asFloatBuffer( );
- public abstract DoubleBuffer asDoubleBuffer( );
- }
Java代码
- /**
- * 1 char = 2 byte,因此 7 个字节的 ByteBuffer 最终只会产生 capacity 为 3 的 CharBuffer。
- *
- * 无论何时一个视图缓冲区存取一个 ByteBuffer 的基础字节,这些字节都会根据这个视图缓冲区的字节顺序设
- * 定被包装成一个数据元素。当一个视图缓冲区被创建时,视图创建的同时它也继承了基础 ByteBuffer 对象的
- * 字节顺序设定,这个视图的字节排序不能再被修改。字节顺序设定决定了这些字节对是怎么样被组合成字符
- * 型变量的,这样可以理解为什么 ByteBuffer 有字节顺序的概念了吧。
- */
- ByteBuffer byteBuffer = ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
- CharBuffer charBuffer = byteBuffer.asCharBuffer();
Java代码
- public abstract class ByteBuffer extends Buffer implements Comparable {
- public abstract short getShort( );
- public abstract short getShort(int index);
- public abstract short getInt( );
- public abstract short getInt(int index);
- ......
- public abstract ByteBuffer putShort(short value);
- public abstract ByteBuffer putShort(int index, short value);
- public abstract ByteBuffer putInt(int value);
- public abstract ByteBuffer putInt(int index, int value);
- .......
- }
Java代码
- // 大端顺序
- int value = buffer.order(ByteOrder.BIG_ENDIAN).getInt();
- // 小端顺序
- int value = buffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
- // 上述两种方法取得的 int 是不一样的,因此在调用此类方法前,请确保字节顺序是你所期望的