在NIO中,数据的读写操作始终是与缓冲区相关联的。Channel将数据
读入缓冲区,然后我们又从缓冲区访问数据。写数据时,首先将要发送的数据按顺序填入缓冲区。基本上,
缓冲区只是一个列表,它的所有元素都是基本数据类型(通常为字节型)。缓冲区是定长的,它不像一些类
那样可以扩展容量(例如,List,StringBuffer等)。注意,ByteBuffer是最常用的缓冲区,因为:1)
它提供了读写其他数据类型的方法,2)信道的读写方法只接收ByteBuffer。
Buffer索引
缓冲区不仅仅是用来存放一组元素的列表。在读写数据时,它有内部状态来跟踪缓冲区的当前位置,以及有效
可读数据的结束位置等。为了实现这些功能,每个缓冲区维护了指向其元素列表的4个索引,如下表所示。
(不久我们将看到如何使用缓冲区的各种方法来修改索引值。)
索引 | 描述 | 存取器/修改器/用法 |
---|---|---|
capacity | 缓冲区的元素总是(不可修改) | int capacity() |
position | 下一个要读/写的元素(从0开始) | int position(), Buffer position(int newPosition) |
limit | 第一个不可读/写元素 | int limit(), Buffer limit(int newLimit) |
mark | 用户选定的position 高的前一个位置或0 | Buffer mark(),Buffer reset() |
position和limit之间的距离指示了可读取/存入的字节数,java中提供了两个方便的方法来计算这个距离。
- boolean hasRemaining():当缓冲区至少还有一个元素时,返回true;
- int remaining():返回缓冲区包含的元素个数;
在这些变量中,始终保持以下关系不变:
0 ≤ mark ≤ position ≤ limit ≤ capacity
mark变量的值“记录”了一个将来可返回的位置,reset()方法则将postion的值还原成上次调用mark()
方法后的position值(除非这样做会违背上面的不变关系);
Buffer创建
通常使用分配空间或包装一个现有的基本类型数组来创建缓冲区。创建ByteBuffer的静态工厂方法,以及
相应的capacity,position,和limit的初始值下表。所有新创建的Buffer实例都没有定义其mark值,
在调用mark()方法前,任何试图使用reset()方法来设置position的值的操作都将抛出InvalidMarkException异常。
方法 | capacity | position | limit |
---|---|---|---|
ByteBuffer allocate(int capacity) | capacity | 0 | capacity |
ByteBuffer allocateDirect(int capacity) | capacity | 0 | capacity |
ByteBuffer wrap(byte[] array) | array.length | 0 | array.length |
ByteBuffer wrap(byte[] array,int offer,int length) | array.length | offset | offset-array.length |
要分配一个新的实例,只需要简单地调用想要创建的缓冲区类型的allocate()静态方法,并指定元素的总数:1
2ByteBuffer byteBuf = ByteBuffer.allocate(20);
DoubleBuffer dblBuf = DoubleBuffer.allocate(5);
在上面代码中,byteBuf分配了20个字节,dblBuf分配了5个Java的
double型数据。这些缓冲区都是定长的,因此无法扩展或缩减它们的容量。如果发现刚创建的缓冲区容量太小,
惟一的选择就是重新创建一个大小合适的缓冲区。
还可以通过调用wrap()静态方法,以一个已有的数组为参数,来创建缓冲区:1
2
3
4byteArray[] = new byte[BUFFERSIZE];
// ...Fill array...
ByteBuffer byteWrap = ByteBuffer.wrap(byteArray);
ByteBuffer subByteWrap = ByteBuffer.wrap(byteArray, 3,3);
通过包装的方法创建的缓冲区 保留了被包装数组内保存的数据。实际上,
wrap()方法只是简单地创建了一个具有指向被包装数组的引用的缓冲区,该数组称为 后援数组。对后援数组
中的数据做的任何修改都将改变缓冲区中的数据,反之亦然。如果我们为wrap()方法指定了偏移量(offset)
和长度(length),缓冲区将使用整个数组为后援数组,同时将position和limit的值初始化为偏移量
(offset)和偏移量+长度(offset+length)。在偏移量之前和长度之后的元素依然可以通过缓冲区访问。
使用分配空间的方式来创建缓冲区其实与使用包装的方法区别不大。惟一的区别是allocate()方法创建了
自己的后援数组。在缓冲区上调用array()方法即可获得后援数组的引用。通过调用arrayOffset()方法,
甚至还可以获取缓冲区中第一个元素在后援数组中的偏移量。使用wrap()方法和非零偏移量参数创建的缓冲区,
其数组偏移量依然是0。
到目前为止,我们实现的所有缓冲区都将数据存放在Java分配的后援数组中。
通常,底层平台(操作系统)不能使用这些缓冲区进行I/O操作。操作系统必须使用自己的缓冲区来进行I/O,
并将结果复制到缓冲区的后援数组中。这些复制过程 可能非常耗费系统资源,尤其是在有很多读写需求的时候。
Java的NIO提供了一种 直接缓冲区(direct buffers) 来解决这个问题。使用直接缓冲区,Java将从平台
能够直接进行I/O操作的存储空间中为缓冲区分配后援存储空间,从而省略了数据的复制过程。这种低层的、
本地的I/O通常在字节层进行操作,因此只能为 ByteBuffer进行直接缓冲区分配 。1
ByteBuffer byteBufDirect =ByteBuffer.allocateDirect(BUFFERSIZE);
通过调用isDirect()方法可以查看一个缓冲区是否是直接缓冲区。由于
直接缓冲区没有后援数组,在它上面调用array()或arrayOffset()方法都将抛出
UnsupportedOperationException异常。在考虑是否使用直接缓冲区时需要牢记几点。首先,要知道调用
allocateDirect()方法并不能保证能成功分配直接缓冲区–有的平台或JVM可能不支持这个操作,因此在
尝试分配直接缓冲区后必须调用isDirect()方法进行检查。其次,要知道 分配和销毁直接缓冲区通常比分配
和销毁非直接缓冲区要消耗更多的系统资源,因为直接缓冲区的后援存储空间通常存在与JVM之外,对它的管理
需要与操作系统进行交互。所以,只有当需要在很多I/O操作上长时间使用时,才分配直接缓冲区。实际上,
在相对于非直接缓冲区能明显提高系统性能时,使用直接缓冲区是个不错的主意。
存储和接受数据
只要有了缓冲区,就可以用它来存放数据了。作为数据的”容器”,缓冲区
既可用来输入也可用来输出。这一点就与流不同,流只能向一个方向传递数据。使用put()方法可以将数据放入
缓冲区,使用get()方法则可以从缓冲区获取数据。信道的read()方法隐式调用了给定缓冲区的put(),而其
write()方法则隐式调用了缓冲区的get()方法。下面展示ByteBuffer的get()和put()方法,当然,其他
类型的缓冲区也有类似的方法。
ByteBuffer:获取和存放字节,有两种类型的get()和put():基于相对位置和基于绝对位置。基于相对位置
的版本根据position的当前值,从”下一个”位置读取或存放数据,然后根据数据量给position增加适当的值
(即,单字节形式增加1数组形式增加array.length, 数组/偏移量/长度形式则增加length)。也就是
说,每次调用put()方法,都是在缓冲区中的已有元素后面追加数据,每次调用get()方法,都是读取缓冲区的
后续元素。不过,如果这些操作会导致position的值超出limit的限制,get()方法将抛出BufferUnderflowException异常,
put()方法将抛出BufferOverflowException异常。例如,如果传给get()方法的目标数组长度大于缓冲区
的剩余空间大小,get()方法将抛出BufferUnderflowException异常,部分数据的get/put是不允许的。
基于绝对位置的get()和put()以指定的索引位置为参数,从该位置读取数据或向该位置写入数据。绝对位置
形式的get和put不会改变position的值。如果给定的索引值超出了limit的限制,它们将抛出IndexOutOfBoundsException异常。
除了字节类型外,ByteBuffer类还提供了其他类型数据的相当位置和绝对位置的get/put方法。这样一来,就有点像DataOutputStream了。
- 相对位置:
- byte get()
- ByteBuffer get(byte[] dst)
- ByteBuffer get(byte[] dst, int offset, int length)
- ByteBuffer put(byte b)
- ByteBuffer put(byte[] src)
- ByteBuffer put(byte[] src, int offset, int length)
- ByteBuffer put(ByteBuffer src)
绝对位置:
- byte get(int index)
- ByteBuffer put(int index, byte b)
ByteBuffer:读取和存放Java多字节基本数据
get () get (int index) - ByteBuffer put
( value) - ByteBuffer put
(int index, value)
其中”
而”
每次调用基于相对位置的put()或get()方法,都将根据特定参数类型的长度增加position的值:short加2,
int加4,等。不过,如果这样做会导致position的值超出limit的限制,get()和put()方法将分别抛出
BufferUnderflowException和BufferOverflowException异常:get和put不允许只对部分数据进行操作。
发生了下溢/上溢(under/overflow)时,position的值不变。可能你已经注意到,很多get/put方法都
返回一个ByteBuffer。实际上它们返回的就是调用它们的那个ByteBuffer。这样做可以实现 链式调用(call chaining),
即第一次调用的结果可以直接用来进行后续的方法调用。例如,可以像下面那样将整数1和2存入ByteBuffer实例
myBuffer中:myBuffer.putInt(1).putInt(2);
多字节数据类型有一个字节顺序,称为big-endian或little-endian。
Java默认使用big-endian。通过使用内置的ByteOrder.BIG_ENDIAN和ByteOrder.LITTLE_ENDIAN实例,
可以获取和设定多字节数据类型写入字节缓冲区时的字节顺序。
- ByteBuffer:缓冲区中的字节顺序
- ByteOrder order()
- ByteBuffer order(ByteOrder order)
第一个方法以ByteOrder常量的形式返回缓冲区的当前字节顺序。第二个方法用来设置写多字节数据时的字节顺序。
下面来看一个使用字节顺序的例子:1
2
3
4
5ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putShort((short) 1);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) 1);
// Predict the byte values for buffer and test your prediction
看了这些有关字节顺序的讨论,你可能希望知道自己的处理器是什么字节
顺序,ByteOrder定义了一个方法来解答这个问题:
ByteOrder:查找字节顺序
static final ByteOrder BIG_ENDIAN
static final ByteOrder LITTLE_ENDIAN
static ByteOrder nativeOrder()
nativeOrder()方法返回常量BIG_ENDIAN或LITTLE_ENDIAN之一。
Buffer准备:clear()、flip()和rewind()
在使用缓冲区进行输入输出数据之前,必须确定缓冲区的position,limit都已经设置了正确的值。首先
我们看看上面三个方法对position和limit的修改操作:
ByteBuffer方法 | 准备Buffer以实现 | position | limit | mark |
---|---|---|---|---|
clear() | 将数据read()/put()进缓冲区 | 0 | capacity | 为定义 |
flip() | 从缓冲区write()/get() | 0 | position | 为定义 |
rewind() | 从缓冲区rewrite()/get() | 0 | unchanged | 为定义 |
三种在Buffer类的源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
*
* <p> Invoke this method before using a sequence of channel-read or
* <i>put</i> operations to fill this buffer. For example:
*
* <blockquote><pre>
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data</pre></blockquote>
*
* <p> This method does not actually erase the data in the buffer, but it
* is named as if it did because it will most often be used in situations
* in which that might as well be the case. </p>
*
* @return This buffer
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
/**
* 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;
}
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*
* <p> Invoke this method before a sequence of channel-write or <i>get</i>
* operations, assuming that the limit has already been set
* appropriately. For example:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
*
* @return This buffer
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
解释:(参考:https://blog.csdn.net/FS1360472174/article/details/52141800)
- clear: clear 并没有真正的清除数据,将position设置为0,limit设置为capacity;
flip :一般是切换到读操作。或者是为写操作准备一个新的序列
eg:重复向一个ByteBuffer写数据的时候,赋值完毕,必须要flip.开始一个新的新序列,否则position
会等于limit,返回空值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public static void main(String[] args) {
byte[] bytes1=new byte[]{1, 6, 3};
ByteBuffer buffer =fromByteArray(bytes1);
System.out.println(buffer);
byte[] bytes2 =new byte[]{1,2,3};
ByteBuffer buffer2=fromByteArray(bytes2);
System.out.println(buffer2);
}
/**
* If you are building up a ByteBuffer by repeatedly writing into it, and then want to give it away, you must remember to flip() it.
* If we did not flip() it, the returned ByteBuffer would be empty because the position would be equal to the limit.
* @param bytes
* @return
*/
public static ByteBuffer fromByteArray(byte[] bytes) {
final ByteBuffer ret = ByteBuffer.wrap(new byte[bytes.length]);
ret.put(bytes);
ret.flip();
return ret;
}
}rewind:倒回,将position 设置为0,重新读取;
压缩Buffer中数据
compact()方法将position与limit之间的元素复制到缓冲区的开始位置,从而为后续的put()/read()调
用让出空间。position的值将设置为要复制的数据的长度,limit的值将设置为capacity,mark则变成未定义。
示例:1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] arg) {
byte[] bytes = new byte[]{1, 6, 3, 1, 1, 1, 1, 1, 1, 1, 1};
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte b = buffer.get();
System.out.println("b:" + b + " position:" + buffer.position()+" limit:"+buffer.limit()+" capacity:"+buffer.capacity());
buffer.compact();
System.out.println("compact position:" + buffer.position()+" limit:"+buffer.limit()+" capacity:"+buffer.capacity());
buffer.flip();
System.out.println("flip position:" + buffer.position()+" limit:"+buffer.limit()+" capacity:"+buffer.capacity());
}
结果如下:
b:1 position:1 limit:11 capacity:11
compact position:10 limit:11 capacity:11
flip position:0 limit:10 capacity:11
为什么要使用这个操作呢?假设你有一个缓冲区要写数据。回顾前面的内容我们知道,对write()方法的非阻塞调用只会写出其能够发送的数据,而不会阻塞等待所有数据发送完。因此write()方法不一定会将缓冲区中的所有元素都发送出去。又假设现在要调用read()方法,在缓冲区中没有发送的数据后面读入新数据。处理方法之一就是简单地设置position = limit和limit = capacity。当然,在读入新数据后,再次调用write()方法前,还需要将这些值还原。这样做有个问题即缓冲区的空间最终将消耗殆尽,如上图中,只剩下一个元素位置可以再存入一个字节。此外,缓冲区前面的空间又被浪费掉了。这就是compact()方法要解决的问题。在调用write()方法后和添加新数据的read()方法前调用compact()方法,则将所有”剩余”的数据移动到缓冲区的开头,从而为释放最大的空间来存放新数据。1
2
3
4
5
6
7
8// Start with buffer ready for reading
while (channel.read(buffer) != -1) {
buffer.flip();
channel.write(buffer);
buffer.compact();
}
while (buffer.hasRemaining())
channel.write(buffer);
注意,如开始已经提到的,复制数据是一个非常耗费系统资源的操作,因此要保守地使用compact()方法。
Buffer透视:duplicate(),slice()等
NIO提供了多种方法来创建一个与给定缓冲区共享内容的新缓冲区,这些方法对元素的处理过程各有不同。基本上,这种新缓冲区有自己独立的状态变量(position,limit,capacity和mark),但与原始缓冲区共享了同一个后援存储空间。任何对新缓冲区 内容 的修改都将反映到 原始缓冲区上 。可以将新缓冲区看作是从另一个角度对同一数据的透视。
duplicate()方法用于创建一个与原始缓冲区共享内容的新缓冲区。新缓冲区的position,limit,mark和capacity都初始化为原始缓冲区的索引值,然而,它们的这些值是相互独立的。如下表格为在ByteBuffer上创建不同透视的方法:
方法 | capacity | position | limit | mark |
---|---|---|---|---|
duplicate() | capacity | position | limit | mark |
slice() | remaining() | 0 | remaining() | 为定义 |
asReadOnlyBuffer() | capacity | position | limit | mark |
asCharBuffer() | remaining()/2 | 0 | remaining()/2 | 为定义 |
asDoubleBuffer() | remaining()/4 | 0 | remaining()/4 | 为定义 |
asFloatBuffer() | remaining()/4 | 0 | remaining()/4 | 为定义 |
asIntBuffer() | remaining()/4 | 0 | remaining()/4 | 为定义 |
asLongBuffer() | remaining()/8 | 0 | remaining()/8 | 为定义 |
asShortBuffer() | remaining()/2 | 0 | remaining()/2 | 为定义 |
由于共享了内容,对原始缓冲区或任何复本所做的改变在所有复本上都可见。
下面回到前面的例子,假设要将在网络上发送的所有数据都写进日志。1
2
3
4
5
6
7// 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()方法,或者你可以一次获取所有的整数,如下所示:1
2
3
4
5
6
7
8
9
10// ...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()方法也将创建新的只读缓冲区。
字符编码
字符是由字节序列进行编码的,而且在字节序列与字符集合之间有各种映射(称为字符集)方式。NIO缓冲区的另一个用途是在各种字符集之间进行转换。要使用这个功能,还需要了解java.nio.charset包中另外两个类:CharsetEncoder和CharsetDecoder类。要进行编码,需要使用一个Charset实例来创建一个编码器并调用encode方法:1
2
3
4Charset charSet = Charset.forName("US-ASCII");
CharsetEncoder encoder = charSet.newEncoder();
ByteBuffer buffer = encoder.encode(CharBuffer.wrap("Hi
mom"));
要进行解码,需要使用Charset实例来创建一个解码器,并调用decode方法:1
2CharsetDecoder decoder = charSet.newDecoder();
CharBuffer cBuf = decoder.decode(buffer);
虽然这种方法能够正常工作,但当需要进行多次编码时,效率就会变得较低。例如,每次调用encode/decode
方法都会创建一个新Byte/CharBuffer实例。其他导致低效率的地方与编码器的创建和操作有关。1
2
3
4
5
6
7
8
9
10encoder.reset();
if (encoder.encode(CharBuffer.wrap("Hi "),buffer,false)
== CoderResult.OVERFLOW) {
// ... deal with lack of space in buffer ...
}
if (encoder.encode(CharBuffer.wrap("Mom"),buffer,true)
== CoderResult.OVERFLOW) {
// ... ditto ...
}
encoder.flush(buffer);
encode()方法将给定CharBuffer转换为一个字节序列,并将其写入给定的缓冲区。如果缓冲区太小,encode()方法的返回值等于CoderResult.OVERFLOW。如果输入的数据完全被接收,并且编码器还准备对更多数据进行编码,encode()方法的返回值则等于CoderResult.UNDERFLOW。另外,如果输入的数据格式有错误,则将返回一个CoderResult对象,并指示了所存在的问题的位置和类型。只有到达了输入数据的结尾时,才将最后的boolean参数设为true。flush()方法将任何缓存的编码数据推送到缓冲区。注意,在新创建的编码器上调用reset()方法并不是必需的,该方法用来重新设置编码器的内部状态,以使其能够进行再次编码。
总结
最后通过一段简短代码展示allocate、flip、get、compact和clear方法对position、limit的修改:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public static void main(String[] arg) throws IOException {
ByteBuffer buffer1 = ByteBuffer.allocate(10);
RandomAccessFile fine = new RandomAccessFile(new File("b.txt"), "rw");
FileChannel fc = fine.getChannel();//分配空间后position为0,limit=capacity=10;
output("allocate", buffer1);
fc.read(buffer1);
output("read", buffer1);
buffer1.flip(); //将limit设置为position,然后将position设置为0
output("flip", buffer1);
buffer1.get(); //将position加一
output("get", buffer1);
buffer1.rewind(); //重置position为0
output("rewind", buffer1);
buffer1.compact(); //将position到limit的数据移到数组前端,并将position设置为(limit-position的距离),limit=capacity
output("compact", buffer1);
buffer1.clear(); //重置position为0,limit=capacity
output("clear", buffer1);
}
public static void output(String tag, ByteBuffer buffer) {
System.out.println(tag + " position:" + buffer.position() + " limit:" + buffer.limit() + " capacity:" + buffer.capacity());
}
结果如下:
allocate position:0 limit:10 capacity:10
read position:6 limit:10 capacity:10
flip position:0 limit:6 capacity:10
get position:1 limit:6 capacity:10
rewind position:0 limit:6 capacity:10
compact position:6 limit:10 capacity:10
clear position:0 limit:10 capacity:10
get position:2 limit:10 capacity:10
参考:
[1] (译文)java中的ShortBuffer https://blog.csdn.net/u010142437/article/details/42082735
[2] Buffer详解 https://blog.csdn.net/guofengpu/article/details/51995730