Java NIO的Buffer
是和其Channel
一起用的。还是那句话,数据从Channel
读入Buffer
,从Buffer
写入Channel
。
一个Buffer
本质上一个可以写入数据的内存块,写入之后,你还可以从中读取数据。一个Buffer
就是一个内存块的包装,同时提供了很多简单的方法来操作它。
Basic Buffer Usage
用Buffer
来读写数据一般分为以下4步:
- 将数据写入
Buffer
,一般是从Channel
读入; - 调用
buffer.flip()
; - 从
Buffer
获取数据; - 调用
buffer.clear()
或者buffer.compact()
。
当你往Buffer
里面写数据时,Buffer
会记录你已经写入了多少数据。如果你想读取数据了,需要调用flip()
方法将Buffer
从写模式切换到读模式。在读模式下,你可以读取所有写入的数据。
一旦数据读完,你需要清理Buffer
以将其重新切回写模式。有两种方式清理:调用clear()
或者compact()
。clear()
方法会清空整个Buffer
,compact()
方法只会清除你以及读取过的数据。所有没有被读取的数据都会移动到Buffer
的头部,并且后续写入的数据都会放到这些数据之后。
以下是一个Buffer
的简单使用示例,其中写、切换、读以及清理操作都进行了注释:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
// 创建一个48字节容量的Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); // 读取数据到Buffer
while (bytesRead != -1) {
buf.flip(); // 切换到读模式
while (buf.hasRemaining()) {
System.out.print((char) buf.get()); // 读取一个字节
}
buf.clear(); // 清理Buffer,使其切换到写模式
bytesRead = inChannel.read(buf);
}
aFile.close();
Buffer Capacity, Position and Limit
如上所述,一个Buffer
本质上一个可以写入数据的内存块,写入之后,你还可以从中读取数据。一个Buffer
就是一个内存块的包装,同时提供了很多简单的方法来操作它。
为了了解Buffer
的工作原理,有三个属性你需要熟悉:
- capacity
- position
- limit
position
和limit
的具体含义依赖于当前Buffer
是读模式还是写模式。capacity
的含义则与Buffer
的模式无关。

Capacity
作为一个内存块,Buffer
有一个确定的固定的大小,称作capacity
。你只能往Buffer
中写入与其capacity
相等数量的字节、长整形数字、字符等。一旦Buffer
满了,在下次写入数据之前,你必须先进行清理(从中读取数据或者清空它)。
Position
当你往Buffer
里写入数据时,一定是写到一个具体的position
。初始position
是0。当一个字节、整数、字符等被写入Buffer
时,position
会前进到Buffer
的下一个单元格,以便插入数据。position
最大取值为capacity - 1
。
当你从Buffer
读取数据时,同样也一定是从一个具体position
读取。当你把一个Buffer
从写模式切换到读模式时,其position
会重置为0。当你从当前position
读取一个数据,position
会前进到下一个要读取的位置。
Limit
在写模式下,limit
代表着你最多可以往Buffer
里写入多少数据(字节、整数、字符等)。同时,在写模式下,limit
和capacity
相等。
切换到读模式后,limit
代表着你从Buffer
中最多能读取多少数据(字节、整数、字符等)。因此,当一个Buffer
切换到读模式时,limit
会被重置为写模式下的position
的值。换句话说,之前写了多少数据,你就能读多少数据(limit
被设置为了写入的数据(字节、整数、字符等)个数,即读模式下的position
的值)。
Buffer Types
Java NIO提供了以下类型的Buffer
:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
如你所见,这些不同类型的Buffer
代表不同类型的数据。换句话说,它们允许你以char
、short
、int
、long
、float
或double
的形式处理缓冲区中的字节。
MappedByteBuffer
有些特别,我会在专门的章节中详细介绍。
Allocating a Buffer
要获得一个Buffer
,需要先分配它。每个类型的Buffer
都有一个allocate()
方法来干这件事。以下是一个分配48字节容量的ByteBuffer
的例子:
ByteBuffer buf = ByteBuffer.allocate(48);
以下是一个分配1024字符容量的CharBuffer
的例子:
CharBuffer buf = CharBuffer.allocate(1024);
Writing Data to a Buffer
有两种方法可以把数据写入Buffer
:
- 从
Channel
将数据写入Buffer
; - 手动调用
put()
方法将数据写入Buffer
。
以下是从Channel
将数据写入Buffer
的例子:
int bytesRead = inChannel.read(buf);
以下是手动写入的例子:
buf.put(127);
还有很多重载的put()
方法,允许你通过各种方式将数据写入Buffer
。比如,将数据写到指定位置,或者写入一个字节数组。详细信息可以参考各个类型的Buffer
的实现类的JavaDoc。
flip()
flip()
方法可以将Buffer
从写模式切换到读模式。调用此方法会将position
重置为0,且limit
会被设置为position
的值。
换句话说,position
现在代表读的位置,而limit
代表写入缓冲区的字节数、字符数等,即最多可以读取多少字节、字符等。
Reading Data from a Buffer
有两种方法从Buffer
中读取数据:
- 将数据从
Buffer
读入Channel
; - 手动调用
get()
方法读取。
以下是将数据从Buffer
读入Channel
的例子:
int bytesWritten = inChannel.write(buf);
以下是手动读取的例子:
byte aByte = buf.get();
同样,还有很多重载的get()
方法,允许你用各种方法读取数据。比如,从指定位置读取,或者读取一个字节数组。详细信息可以参考各个类型的Buffer
的实现类的JavaDoc。
rewind()
rewind()
会方法将position
重置为0。因此你可以重新读取Buffer
里的所有数据。limit
会保持不变,依然代表最多可以从Buffer
读取的数据量。
clear() and compact()
一旦读完Buffer
中的数据,就必须使其为再次写入做好准备。你可以通过调用clear()
或compact()
来实现这一点。
如果你调用了clear()
方法,position
会被重置为0,limit
也会被设置为capacity
。换句话说,Buffer
被清空了。但Buffer
里的数据并没有被清理。仅仅是标记了你可以在哪写入数据。
如果你调用clear()
方法的是,Buffer
里面还有未读取的数据,这些数据会被遗忘,意味着再也什么标记可以让你知道哪些数据是被读取过的,哪些还没有被读取。
如果Buffer
里面还有数据,你想之后读取它们,现在你想先写入些数据,那么你可以调用compact()
方法代替clear()
方法。
compact()
方法会将所有未读取的数据拷贝到Buffer
的头部。然后设置position
为最后一个未读数据的后面位置。和clear()
一样,limit
还是被设置为capacity
。现在Buffer
已经做好写入的准备了,但不用担心覆盖未读取的数据。
mark() and reset()
通过调用mark()
方法,你可以给Buffer
打一个位置标记。以后你可以通过调用reset()
方法将position
设置为之前打的标记的位置。如下:
buffer.mark();
// 调用buffer.get()一次或多次读取数据。
buffer.reset();
equals() and compareTo()
你可以使用equals()
和compareTo
比较两个Buffer
。
equals()
两个Buffer
是相等的,当:
- 它们的类型一样(字节、字符、整数等);
- 它们剩余的(未读取的)数据(字节、字符、整数等)量相同;
- 所有剩余的数据(字节、字符、整数等)都相等。
如你所见,equals()
方法仅仅比较Buffer
的一部分,并不是其内部的每一个元素。事实上,它只比较剩余的元素。
compareTo()
compareTo()
方法比较两个Buffer
中剩余的元素(字节、字符等),以便进行排序。一个Buffer
被认为比另一个小,当:
- 第一个与对应元素不相等的元素小于另一个
Buffer
中对应的元素 ; - 所有元素都相等,但第一个
Buffer
先于第二个被读完(它的元素更少)。
说明
发现貌似有人在看这个系列文章了,有必要说明下,这个Java NIO系列来源于jenkov.com,本文只是翻译,希望大家千万不要误会,本文不是原创。原文地址:Java NIO。
网友评论