Java NIO Buffer(缓冲区)通常与 NIO Channels(通道)一起使用。如我们 前面 所提到的那样,数据从 channel 中读取到 buffer 中,也可以从 buffer 写入到 channel 中。

Buffer 本质上是一个可以写入数据的内存块,并且之后你又可以从中再次读取数据。 此内存块包含在 NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。

Buffer 基本用法

使用 Buffer 去读取和写入数据时,通常要遵循以下 4 个步骤:

  1. 写入数据到 Buffer 中
  2. 调用 buffer.flip()
  3. 从 Buffer 中读取数据
  4. 调用 buffer.clear()buffer.compact() 方法

将数据写入缓冲区时,缓冲区会跟踪你写入的数据量。一旦你需要从中读取数据时,你需要调用 flip() 方法,将缓冲区从写入模式切换成读取模式。在读取模式下,你可以读取到所有写入到缓冲区的数据。

一旦你读取到了所有的数据,你需要对缓冲区做一次清理(clear),让其为下一次写入数据做好准备。你可以采用以下两种方式:一是调用 clear() 方法,二是调用 compact() 方法。clear() 方法是对整个缓冲区做清理。compcat() 方法只是清理掉你已经读取过的数据,任何未读取过的数据会被移动到缓冲区的起始位置,新写入进来的数据将会放到这些未读取的数据后面。

我们来看一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RandomAccessFile fromFile = new RandomAccessFile("/Users/wangwei/Desktop/fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();

// create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

// read data from channel into buffer
int bytesRead = fromChannel.read(buf);
while (bytesRead != -1) {

// switch to read mode
buf.flip();

while (buf.hasRemaining()) {
System.out.println((char) buf.get());
}

// make buffer ready for writing
buf.clear();
bytesRead = fromChannel.read(buf);
}

fromChannel.close();
fromFile.close();

Buffer 容量、位置与限制

Buffer 本质上是一个可以写入数据的内存块,并且之后你又可以从中再次读取数据。 此内存块包含在 NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。

为了理解 Buffer 的工作原理,你需要去熟悉 Buffer 的三个属性:

  • capacity(容量)
  • position(位置)
  • limit(限制)

position 与 limit 的具体含义取决于 Buffer 处于哪种模式,而 capacity 则与 Buffer 的模式无关。示意图如下:

JavaNIOBuffer

Capacity

作为一个内存块,缓冲区具有一定的固定大小,也称为 “容量”。 你最多只能写入容量大小的字节 (bytes)、长整型 (longs)、字符 (chars) 等数据。一旦缓冲区满了,你需要清空它,才能继续写入数据。

Position

当你写入数据时,你需要在某个 position 处才能执行此操作。初始位置为 0,当一个字节、长整数等已写入缓冲区时,position 被提前指向缓冲区中的下一个单元以备插入新的数据。 Position 的最大值为 capacity - 1.

当你从缓冲区读取数据时,也需要从给定位置开始读取数据。当你将缓冲区从写入模式切换到读取模式时,position 将重置为 0。当你从缓冲区读取数据时,将从 position 所指定的位置开始读取数据,并将 position 提前设置到下一个要读取的位置。

Limit

在写入模式下,Buffer 的限制是你可以写入缓冲区的数据量的限制,limit 就等于 capacity。

将 buffer 切换为读取模式时,limit 就变成了你可以从中读取到的最大数据量。 因此,当 Buffer 切换为读取模式时,limit 被设置为写入模式的写入位置。 换句话说,就是你可以读取到的写入字节数。

Buffer 类型

Java NIO 有以下几种 Buffer 类型:

IO_NioBuffer

如你所见,这些 Buffer 类型分别代表了不同的数据类型。 换句话说,它们允许你使用 char,short,int,long,float 或 double 来处理缓冲区中的字节。

MappedByteBuffer 有点特殊,以后单独介绍。

使用

Allocating a Buffer

直接调通 Buffer.allocate() 就可以获取一个 Buffer 对象,示例代码:

1
2
3
4
5
// 48 bytes
ByteBuffer buffer = ByteBuffer.allocate(48);

// 1024 characters
CharBuffer charBuffer = CharBuffer.allocate(1024);

写入数据

Buffer 有两种写入数据的方式:

  1. 从 Channel 写入到 Buffer

    1
    int bytesRead = inChannel.read(buf); //read into buffer.
  2. 直接调用 buffer.put () 方法

    1
    buf.put(127);    

    有关 put () 的更多用法,具体参见 JavaDoc 文档.

flip()

前面说过,flip () 用于切换 Buffer 的运行模式,并相应地改变 position 与 limit 的值。

buffer_lifecycle

读取数据

Buffer 有两种方式可以读取数据:

  1. 从 Buffer 读取数据到 Channel 中。

    1
    2
    //read from buffer into channel.
    int bytesWritten = inChannel.write(buf);
  2. 直接调用 get () 方法获取数据。

    1
    byte aByte = buf.get();

    有关 get () 的更多用法,具体参见 JavaDoc 文档.

rewind()

Buffer.rewind () 方法会将 position 重置为 0,这样就可以重新读取所有的数据。但 limit 的值不受影响,因此这就导致会读取出很多空的元素数据。

clear () 和 compcat ()

完成从缓冲区读取数据后,必须使缓冲区准备好再次写入。 你可以通过调用 clear() 或调用 compact() 来完成此操作。

如果你调用 clear() 方法,position 会设置为 0,limit 会设置为 capacity ,但是 Buffer 中的数据并没被真正删除掉。只有标记告诉你可以将数据写入缓冲区的位置。

如果在调用 clear() 时缓冲区中有任何未读数据,数据将被 “遗忘”,这意味着你不再有任何标记告诉读取了哪些数据,以及未读取的数据。

如果缓冲区中仍有未读数据,并且你想稍后读取它,但你需要先进行一些写入,请调用 compact() 而不是 clear()

compact() 将所有未读数据复制到 Buffer 的开头。然后它将位置设置在最后一个未读元素之后。 limit 属性仍设置为 capacity,就像 clear() 一样。现在缓冲区已准备好写入,但你不会覆盖未读数据。

mark () 和 reset ()

你可以通过调用 Buffer.mark() 方法在 Buffer 中标记给定位置。 然后,你可以通过调用 Buffer.reset() 方法将位置重置回标记位置。 示例代码:

1
2
3
4
5
6
// 设置 mark 值等于 position
buffer.mark();

//call buffer.get() a couple of times, e.g. during parsing.
//set position back to mark.
buffer.reset();

equals () 和 comareTo ()

可以使用 equals()compareTo() 这两个方法来比较两个缓冲区。

equals()

两个 Buffer 如果满足以下条件,则表示相等:

  • 类型一致
  • 剩余的字节、字符等数量相同
  • 剩余的字节、字符内容都相同

如你所见,equals 仅比较缓冲区的一部分,而不是它内部的每个元素。 实际上,它只是比较缓冲区中的剩余元素。

compareTo()

compareTo() 方法比较两个缓冲区的剩余元素(字节,字符等)的大小。 在下列情况下,缓冲区被视为 “小于” 另一个缓冲区:

  • 与另一个缓冲区中的对应元素相等的第一个元素小于另一个缓冲区中的元素。
  • 所有元素都相等,但第一个缓冲区在第二个缓冲区之前耗尽了元素(它只有更少的元素)。

参考资料