NIO提供了多种方法来创建一个与给定缓冲区共享内容的新缓冲区,这些方法对元素的处理过程各有不同。基本上,这种新缓冲区有自己独立的状态变量(position,limit,capacity和mark),但与原始缓冲区共享了同一个后援存储空间。任何对新缓冲区内容的修改都将反映到原始缓冲区上。可以将新缓冲区看作是从另一个角度对同一数据的透视。表5.4列出了相关的方法。
duplicate()方法用于创建一个与原始缓冲区共享内容的新缓冲区。新缓冲区的position,limit,mark和capacity都初始化为原始缓冲区的索引值,然而,它们的这些值是相互独立的。
表 5.4:在Buffer上创建不同透视的方法
方法 | Capacity | 新缓冲区的初始值 | ||
Position | Limit | Mark | ||
ByteBuffer duplicate() | capacity | position | limit | mark |
ByteBuffer slice() | remaining() | 0 | remaining() | 未定义 |
ByteBuffer asReadOnlyBuffer() | capacity | position | limit | mark |
CharBuffer asCharBuffer() | remaining()/2 | 0 | remaining()/2 | 未定义 |
DoubleBuffer asDoubleBuffer() | remaining()/8 | 0 | remaining()/8 | 未定义 |
FloatBuffer asFloatBuffer() | remaining()/4 | 0 | remaining()/4 | 未定义 |
IntBuffer asIntBuffer() | remaining()/4 | 0 | remaining()/4 | 未定义 |
LongBuffer asLongBuffer() | remaining()/8 | 0 | remaining()/8 | 未定义 |
ShortBuffer asShortBuffer() | remaining()/2 | 0 | remaining()/2 | 未定义 |
由于共享了内容,对原始缓冲区或任何复本所做的改变在所有复本上都可见。下面回到前面的例子,假设要将在网络上发送的所有数据都写进日志。
// Start with buffer ready for writing
ByteBuffer logBuffer = buffer.duplicate();
while (buffer.hasRemaining()) // Write all data to network
networkChannel.write(buffer);
while (logBuffer.hasRemaining()) // Write all data to logger
loggerChannel.write(buffer);
注意,使用了缓冲区复制操作,向网络写数据和写日志就可以在不同的线程中并行进行。
slice()方法用于创建一个共享了原始缓冲区子序列的新缓冲区。新缓冲区的position值是0,而其limit和capacity的值都等于原始缓冲区的limit和position的差值。slice()方法将新缓冲区数组的offset值设置为原始缓冲区的position值,然而,在新缓冲区上调用array()方法还是会返回整个数组。
Channel在读写数据时只以ByteBuffer为参数,然而我们可能还对使用其他基本类型的数据进行通信感兴趣。ByteBuffer能够创建一种独立的"视图缓冲区(view buffer)",用于将ByteBuffer的内容解释成其他基本类型(如CharBuffer)。这样就可以从该缓冲区中读取(写入数据是可选操作)新类型的数据。新缓冲区与原始缓冲区共享了同一个后援存储空间,因此,在任一缓冲区上的修改在新缓冲区和原始缓冲区上都可以看到。新创建的视图缓冲区的position值为0,其内容从原始缓冲区的position所指位置开始。这与slice()操作非常相似。不过,由于视图缓冲区操作的是多字节元素,新缓冲区的capacity和limit的值等于剩余总字节数除以每个该类型元素对应的字节数(例如,创建DoubleBuffer时则除以8)。
下面来看一个例子。假设通过某个Channel接收到一条消息,该消息由一个单独字节,后跟大量big-endian顺序的双字节整数(如short型)组成。由于该消息是通过Channel送达的,它一定在一个ByteBuffer中,在此为buf。消息的***个字节包含了消息中双字节整数的数量。你可能要调用***个字节指定次数的buf.getShort()方法,或者你可以一次获取所有的整数,如下所示:
// ...get message by calling channel.read(buf) ...
int numShorts = (int)buf.get();
if (numShorts < 0) {
throw new SomeException()
} else {
short[] shortArray = new short[numShorts];
ShortBuffer sbuf = buf.asShortBuffer();
sbuf.get(shortArray); // note: will throw if header was incorrect!
}
asReadOnlyBuffer()方法的功能与duplicate()方法相似,只是任何会修改新缓冲区内容的方法都将抛出ReadOnlyBufferException异常。包括各种型式的put(),compact()等,甚至连在缓冲区上调用无方向性的array()和arrayOffset()方法也会抛出这个异常。当然,对产生这个只读缓冲区的非只读缓冲区进行的任何修改,仍然会与新的只读缓冲区共享。就像用duplicate()创建的缓冲区一样,只读缓冲区也有独立的缓冲区状态变量。可以使用isReadOnly()方法来检查一个缓冲区是否是只读的。如果原缓冲区已经是只读的,调用duplicate()或slice()方法也将创建新的只读缓冲区。