Java NIO Buffers 和 NIO Channel 一起使用。正如你所知,数据从通道(channel)中读取到缓冲区(buffers),或从缓冲区(buffer)把数据写入到通道(channels) .

阅读文章的过程中如果有任何疑问,欢迎添加笔者为好友,拉您进【七日书摘】微信交流群,一起交流技术,一起打造高质量的职场技术交流圈子,抱团取暖,共同进步。
七日书摘官方群.jpg

一个缓冲区(Buffer)本质上就是一块可以用来读写数据的内存区。这块内存被包裹在 NIO Buffer 对象中(注:本质就是在内存块域中创建的一个byte数组),对外提供一系列的使你能够方便操作该内存块的方法。

Buffer 基本用法(Basic Buffer Usage)

使用缓冲区(Buffer) 读写数据,通常遵循四个步骤:

  1. 把数据写入缓冲区(Buffer);
  2. 调用 flip;
  3. 从缓冲区(Buffer)中读取数据;
  4. 调用 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)是如何工作的:

  1. capacity 容量
  2. position 位置
  3. limit 限制

position 和 limit 的具体含义取决于当前缓冲区(Buffer)是读模式或是写模式。capacity 在这两种模式下都表示缓冲区(Buffer)的容量。

下面有张示例图,描诉了不同模式下 position 和 limit 的含义:
buffers-modes.png
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):

  1. 从 Channel 中向缓冲区(Buffer)写数据;
  2. 通过调用缓冲区(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)读数据也有两种方式:

  1. 从缓冲区(Buffer)读数据写入到 Channel;
  2. 使用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)是否相等,需要满足:

  1. 类型相同(byte,char,int等等)
  2. 缓冲区(Buffer)中剩余的字节、字符大小相同
  3. 所有剩余的字节相同

从上面的条件可以看出,equals 只会比较缓冲区(Buffer)中的部分内容,而不是缓冲区(Buffer)的每一个元素。

compareTo()

compareTo() 方法也是比较两个缓冲区中剩余元素(字节、字符等等),以便在排序过程中使用。一个缓冲区小于另一个缓冲区需要满足以下的比较条件:

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

英文原文链接:http://tutorials.jenkov.com/java-nio/buffers.html

------完------

推荐阅读:

Java NIO 简明教程 之 Java NIO 概述

Java NIO 简明教程 之 Java NIO Channel

Java基础知识面试题篇(2020年2月最新版)

更技术学习请进入七日书摘官方群: 七日书摘官方群

七日书摘官方群群聊二维码.png

参考资源:
https://blog.csdn.net/u013565163/article/details/83539738
https://blog.csdn.net/Andrew_Yuan/article/details/80215164