Java NIO Buffers 是与 Channels一起组合使用的。
Buffer本质上是一块内存区,我们可以写入数据,然后再读出来。这个内存区被包装为 NIO Buffer
对象, 这个对象提供了一组方法以方便我们操作内存区。
基础使用
使用 Buffer
读、写数据通常有下面4个步骤:
- 写数据到Buffer
- 调用
buffer.flip()
- 从Buffer读出数据
- 调用
buffer.clear()
或者是buffer.compact()
当我们把数据写到 buffer 时,buffer会保持跟踪写了多少数据进去。一旦需要读数据,就需要调用 flip()
方法来转换 buffer 从写模式切到读模式。
在读模式下,buffer 可以让我们读到所有刚才写入的数据。
一旦我们读取了所有数据,就需要 clear buffer
, 以便于再次转换为写模式,有两种方式可以实现cleat buffer
:
- 调用
clear()
- 调用
compact()
需要说明一下,clear()
会清除整个buffer;compact()
仅清除已经读过的数据,未读的数据会被移动到buffer的头部,而且数据可以写入到未读数据的后面。
下面是一个简单的 Buffer 使用示例。
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
}
aFile.close();
Buffer 容量(Capacity), 位置(Position) and 限制(Limit)
Buffer本质上是一块内存区,我们可以写入数据,然后再读出来。这个内存区被包装为 NIO Buffer
对象, 这个对象提供了一组方法以方便我们操作内存区。
为了了解Buffer的工作原理,我们需要熟悉它的三个属性:
- Capacity
- Position
- Limit
Position
和Limit
的含义依赖于Buffer
所处于的是读模式还是写模式。Capacity
在读写模式中都是一样的。
下面这张示例图说明了读写模式下三个属性的说明:
Capacity
Buffer 作为内存块, 有一个确定的大小, 叫做 capacity
. 我们仅能写 capacity
bytes,longs,chars等类型数据到Buffer中。一旦Buffer被写满了,如果要继续写就需要清空Buffer。
Position
要分为两种情况来看。
写模式:当我们写数据到Buffer时,是从一个确定的position
开始,初始标记为 0
。当有数据写入到Buffer时,position
会提前的指向Buffer的下一个cell
以便于插入数据。position
最大是 capacity
- 1.
读模式:当我们从Buffer中读数据时,同样是从一个确定的position
开始。当我们把Buffer的模式从写模式切(flip
)到读模式时,position
就会重置为 0
。当我们从Buffer中读数据时,是从 position
指向的数据读出来的,同时position
会提前指向下一个position
来读数据。
Limit
写模式:在写模式下,limit
是指每次能写多少数据到buffer中。换句话说,写模式下 limit
等于 capacity
.
读模式:当切换Buffer到读模式下时,limit
表示每次能读到的数据的大小。因此,limit
等于写模式下的 position
。换句话说,我们读多少数据取决于写入了多少。再换句话说,limit
等于写入数据的大小(number of bytes), 这个大小是标记为position
的。
Buffer 的类型
Java NIO 包含了下面这些 Buffer 类型:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer类型分别表示类不同数据类型。MappedByteBuffer
有些特殊,后面再讲。
为Buffer分配大小
先分配才能获取buffer。每一个Buffer类都有一个allocate()
方法来分配。
举个例子,为 ByteBuffer
分配48 bytes大小的容量(capacity)
ByteBuffer buf = ByteBuffer.allocate(48);
写数据到Buffer
有两种方式可以把数据写入到Buffer中:
- 从 Channel 中把读出数据写到 Buffer中
- 通过 Buffer提供的
put()
方法把数据写到 Buffer中
方式一示例:
int bytesRead = inChannel.read(buf); //read into buffer.
方式二示例:
buf.put(42);
有很多其他类型的put()
方法来写数据到Buffer中。这里不再赘述
flip() 切到读模式
flip()
方法是用来实现从写模式切换到读模式的。当调用flip()
方法时,会把position
重置为0,同时会把limit
置为 position
重置为0之前的位置。换句话说,limit
现在表示的是写入的数据大小——有多少数据可以被读出来。
从Buffer读数据
有两种方式可以从Buffer中读取数据:
- 从buffer中读数据到channel
- 通过Buffer提供的
get()
方法来读
方式一示例:
int bytesWritten = inChannel.write(buf); //read from buffer into channel.
方式二示例:
byte aByte = buf.get();
有很多其他类型的get()
方法来实现从buffer中读数据。这里不再赘述
rewind()
这个方法Buffer.rewind()
, 可以把position
重置为0,因此可以从Buffer中读取所有数据。limit
保持不变,仍然表示有多少数据可以从Buffer中读取。
PS: 注意下flip()
与rewind()
的区别。两者都会把position
置为0,但flip()
会把limit
置为position
重置为0之前的位置,而rewind()
不会对limit
有改变。
clear() and compact()
在我们从Buffer中读取数据之后,就需要把Buffer置为可写模式,可以通过调用clear()
或者compact()
来实现。
clear()
调用clear()
方法后,position
会被置为0, limit
被置为与capacity
相同大小。事实上此时数据并未被清除,只是被标记为了可写。
调用clear()
时如果还有未被读取的数据,则这部分数据就再也没有机会被读到了,因为并不清楚这部分数据时从哪里开始的。
compact()
当然了,如果有需求希望把Buffer转换为写模式而又不丢失未被读取的数据时,可以使用compact()
方法。
compact()
会复制所有未被读取的数据到Buffer的头部,然后会重置position
到最后一个未被读取数据的地方,limit
同样的会被置为与capacity
相同大小。
mark() and reset()
可以通过mark()
方法来标记Buffer的position
值。当往后读取了一部分后,可以通过调用reset()
方法来回到上一步标记的地方(position
会重新置为标记的地方)。
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
equals() and compareTo()
怎么比较两个Buffer是否相等呢?可以使用equals()
或者compareTo()
方法。
equals()
什么条件下两个Buffer是相等(equals)的呢?
- 存储的数据类型是一致的(byte, char, int 等等)
- 剩余可用数据大小相同
- 剩余可用数据类型相同
equals()
只比较Buffer的一部分,并不比较每一个元素,而是只对比剩余元素。
compareTo()
compareTo()
方法比较两个Buffer的剩余元素,在排序时会用到。
什么条件下一个Buffer会比另一个Buffer更小呢?
- A Buffer的第一个元素小于B Buffer第一个相同元素
- 所有元素相等,但A Buffer比B Buffer更早的读取完数据,也就是说A元素少