Java NIO Buffers 和 NIO Channel 一起使用。正如你所知,数据从通道(channel)中读取到缓冲区(buffers),或从缓冲区(buffer)把数据写入到通道(channels) .
阅读文章的过程中如果有任何疑问,欢迎添加笔者为好友,拉您进【七日书摘】微信交流群,一起交流技术,一起打造高质量的职场技术交流圈子,抱团取暖,共同进步。
一个缓冲区(Buffer)本质上就是一块可以用来读写数据的内存区。这块内存被包裹在 NIO Buffer 对象中(注:本质就是在内存块域中创建的一个byte数组),对外提供一系列的使你能够方便操作该内存块的方法。
Buffer 基本用法(Basic Buffer Usage)
使用缓冲区(Buffer) 读写数据,通常遵循四个步骤:
- 把数据写入缓冲区(Buffer);
- 调用 flip;
- 从缓冲区(Buffer)中读取数据;
- 调用 buffer.clear() 或者 buffer.compact()
当你向缓冲区(Buffer) 写入数据时,缓冲区(Buffer) 会记录已经写入的数据大小。当需要读数据时,需通过 flip() 方法把缓冲区(Buffer) 从写模式切换成读模式;在读模式下,可以读取所有已经写入的数据。
当你读取完数据后,需要清空缓冲区(Buffer),以满足向缓冲区(Buffer) 再次写入。清空缓冲区(Buffer) 有两种方式:调用clear()
或 compact()
方法。clear()
会清空整个缓冲区(Buffer),compact()
仅只清空已读取的数据,未被读取的数据会被移动到缓冲区(Buffer) 的开始位置,写入位置则紧跟着未读数据之后。
下面是一个使用缓冲区(Buffer)的简单示例,包括了 write、flip 和 clear 操作:
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)的容量,位置,上限(Buffer Capacity, Position and Limit)
缓冲区(Buffer)本质上就是一块可以用来读写数据的内存区。这块内存被包裹在 NIO Buffer 对象中(注:本质就是在内存块域中创建的一个byte数组),对外提供一系列的使你能够方便操作该内存块的方法。
一个缓冲区(Buffer)有三个重要属性是必须掌握的,它能使你了解缓冲区(Buffer)是如何工作的:
- capacity 容量
- position 位置
- limit 限制
position 和 limit 的具体含义取决于当前缓冲区(Buffer)是读模式或是写模式。capacity 在这两种模式下都表示缓冲区(Buffer)的容量。
下面有张示例图,描诉了不同模式下 position 和 limit 的含义:
Buffer capacity, position and limit in write and read mode.
注1:在写模式下 limit 和 capacity 意义一样表示可写的内存的大小,position 指向当前已写到了那块内存地址,初始指向 0,最大 limit/capacity;
注2:在读模式下 limit 表示本次可读的最大数据,position 表示当前读到了那一块内存地址,limit 到 capacity 表示可写的内存;
容量(Capacity)
创建一个缓冲区(Buffer)的时候实际上创建的是一块固定大小的内存,缓冲区(Buffer)的这个固定大小叫做 capacity。也就是你只能写入 capacity 大小的字节、长整型、字符等。一旦缓冲区写满,如果想要继续写数据,就需要读取缓冲区中的数据或者直接清除缓冲区中的数据。
位置(Position)
当向缓冲区(Buffer)写入数据的时候,开始位置(position)必须确定。位置(position)初始值为 0,当你写入一个字节,长整型到缓冲区(Buffer)时,位置(position)就会指向写入数据之后的内存单元,注意位置(position)最大取值为 capacity-1.
当从缓冲区(Buffer)读取数据时,也需要从一个确定的位置(position)开始。缓冲区(Buffer)从写入模式变为读取模式时,位置(position)会归零,每次读取后,位置(position)向后移动。
上限(Limit)
在写模式,limit 的含义是我们所能写入的最大数据量。它等同于缓冲区(Buffer)的容量(capacity)。
一旦切换到读模式,limit 则代表我们所能读取的最大数据量,他的值等同于写模式下 position 的位置。数据读取的上限是缓冲区(Buffer)中已有的数据,也就是limit 的位置(原 position 所指的位置)。
缓冲区类型(Buffer Types)
Java NIO 提供了下面几种具体缓冲区(Buffer)类型:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
正如你所见,缓冲区(Buffer)支持不同的数据类型。换言之,缓冲区(Buffer)中的数据可以是char、short、int、long、float、double基本类型。
MappedByteBuffer 是稍有不同,将在后续的章节中单独介绍。(注:常会用来操作大文件)
分配一个缓冲区(Allocating a Buffer)
想要获得缓冲区(Buffer)对象,必须先要分配它。每个缓冲区(Buffer)类都有一个 allocate()
方法来执行该操作。
下面是一个分配 48 字节的大小缓冲区(Buffer)示例:
ByteBuffer buf = ByteBuffer.allocate(48);
下面是一个分配 1024 字节的大小缓冲区(Buffer)示例:
CharBuffer buf = CharBuffer.allocate(1024);
写入数据到缓冲区(Writing Data to a Buffer)
可以通过以下两种方式把数据写入缓冲区(Buffer):
- 从 Channel 中向缓冲区(Buffer)写数据;
- 通过调用缓冲区(Buffer)的 put() 方法写入数据;
下面是一个从 Channel 写数据到缓冲区(Buffer)的示例:
int bytesRead = inChannel.read(buf); //read into buffer.
下面是通过使用 put()
方法写入数据到缓冲区(Buffer):
buf.put(127);
put()
方法有多个重载版本,支持不同的方式写入数据到缓冲区(Buffer)。例如:把数据写入指定位置,或写入数组字节。关于缓冲区(Buffer)更多的操作 见JavaDoc 文档详细说明。
翻转(flip())
flip()
方法可以把缓冲区(Buffer)从写模式切换到读模式。调用flip()
方法会把 position 归零,并设置limit 为之前的 position 的值。也就是说,现在position 代表的是读取位置,limit 标示的是已写入的数据位置。
从缓冲区读取数据(Reading Data from a Buffer)
缓冲区(Buffer)读数据也有两种方式:
- 从缓冲区(Buffer)读数据写入到 Channel;
- 使用
get()
方法从缓冲区(Buffer)读数据;
下面是一个从缓冲区(Buffer)读数据写入到 channel 的示例:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
下面的是一个使用get()
方法从缓冲区(Buffer)读数据的示例:
byte aByte = buf.get();
get()
也多个版本的重载方法,对应不同的读取方法。
rewind()
调用 rewind() 方法将 position 置为 0,这样可以重复的读取缓冲区(Buffer)中的数据。limit 保持不变。
clear() 和 compact()
一旦读完缓冲区中的数据后,你必须保证缓冲区(Buffer)能够被再次写入,需要调用 clear()
和 compact()
方法来调整 position,limit。
当你调用clear()
方法将 position 设置为 0,limit被设置为 capacity 。看起来缓冲区(Buffer)的数据被清空了,实际上数据并没有被清除,只是调整了positon、limit 使缓冲区(Buffer)能够被再次写入覆盖之前的数据,即使数据没有被读取。
如果有未读的数据,你并不想他们被覆盖,那么你可使用compact()
方法代替 clear()
方法。
compact()
将拷贝所有未读的数据放在缓冲区(Buffer)的开始位置,此时 position 并不会被设置为 0,而是未读的数据长度,limit 等于 capacity 和 clear()
方法的效果一样,缓冲区(Buffer)可以再次被写入,但是不用担心会覆盖之前的数据。
mark() 和 reset()
你可以通过 Buffer.mark()
方法可以标记当前的 position ,在 mark() 方法之后也可以通过 Buffer.reset()
来复位 position。
以下是一个示例:
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
equals() 和 compareTo()
可以使用 equals()
和 compareTo()
比较两个缓冲区(Buffer)。
equals()
比较两个缓冲区(Buffer)是否相等,需要满足:
- 类型相同(byte,char,int等等)
- 缓冲区(Buffer)中剩余的字节、字符大小相同
- 所有剩余的字节相同
从上面的条件可以看出,equals 只会比较缓冲区(Buffer)中的部分内容,而不是缓冲区(Buffer)的每一个元素。
compareTo()
compareTo() 方法也是比较两个缓冲区中剩余元素(字节、字符等等),以便在排序过程中使用。一个缓冲区小于另一个缓冲区需要满足以下的比较条件:
- 第一个元素小于另一个缓冲区中对应的元素。
- 所有的元素都是相等的,但是第一个缓冲区在第二个缓冲区之前耗尽了元素(它的元素更少)。
------完------
推荐阅读:
Java NIO 简明教程 之 Java NIO Channel
参考资源:
https://blog.csdn.net/u013565163/article/details/83539738
https://blog.csdn.net/Andrew_Yuan/article/details/80215164