NIO03——Buffer

一、Buffer 简介
 Java NIO 中的 Buffer 用于和 Channell 交互,像之前说的那样,数据从 Channel 读入到 Buffer,从 Buffer 写入到 Channel。
 Buffer 本质上是一块内存,你可以向其中写入数据,之后再从中读取你写入的数据。这一块内存被包装成了一个 NIO Buffer 对象,这个对象提供了一些便于访问该内存的方法。

二、Buffer 的基本用法
 使用 Buffer 读写数据通常遵循以下四个步骤:
  ①将数据写入 Buffer
  ②调用 Buffer 的 flip() 方法
  ③从 Buffer 中读出数据
  ④调用 Buffer 的 clear() 或 compact() 方法
 当你向一个 Buffer 中写入数据的时候,这个 Buffer 会跟踪你已经写入了多少数据。一旦你需要读取数据的时候,则需要调用 flip() 方法将 Buffer 从写模式切换到读模式,在读模式下你可以读取到写入的所有数据。
 一旦完成了读操作,则需要清空这个 Buffer,让它可以再次写入数据。清空 Buffer 有两种方式:调用 clear() 方法或调用 compact() 方法。调用 clear() 会清理整个 Buffer;而调用 compact() 则只会清理你已经读过的数据,所有未读过的数据都会移动到 Buffer 的开头,这时新数据会写在这些数据之后。
 下面是一个 Buffer 的简单示例,使用了 write、flip、read 和 clear 操作:

RandomAccessFile aFile = new RandomAccessFile("F:\\temp\\file.txt", "rw");
FileChannel inChannel = aFile.getChannel();
// 创建Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
// 将Channel中的数据写入Buffer
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
	buf.flip();// 切换到读模式
	while (buf.hasRemaining()) {
		System.out.print((char) buf.get());// 从Buffer中读数据
	}
	buf.clear();// 清空Buffer,使其可以再次进行写入操作
	bytesRead = inChannel.read(buf);// 再次写入
}
aFile.close();

三、Buffer 的 capacity、position 和 limit 属性
 想要理解 Buffer 的工作方式,就需要熟悉 Buffer 的三个属性:

  • capacity
  • position
  • limit

 position 和 limit 的意义取决于 Buffer 的读写模式,但 capacity 的意义则不关乎 Buffer 的模式是读还是写。下图是不同模式下的 position、limit 和 capacity:
在这里插入图片描述
 以下是这几个属性的详细解释:
 1、capacity
  Buffer 作为一个内存块,有一个确定的大小,就是它的容量,你可以向 Buffer 中写入 capacity 个 byte 或者 long 或者 char 等类型的数据。一旦 Buffer 满了,你就需要将其清空(将数据读取并清空 Buffer)以便于向其中写入更多的数据。
 2、position
  当向 Buffer 中写数据的时候,position 表示当前写入的位置。初始的 position 值为 0。当一个 byte 或者 long 或者其他基本类型数据写入到 Buffer 之后,position 的就会指向 Buffer 的下一个位置(Buffer的底层是一个数组)以便于下一次写入数据。
  当从 Buffer 中读数据的时候,也需要一个给定的 position 值,以便知道是从 Buffer 的哪个位置读取数据。当调用 Buffer 的 flip() 从写模式切换到读模式时,position 的值会被重置为 0,以从 Buffer 的起始位置开始读数据。读取当前 position 位置的数据后 position 同样会指向 Buffer 的下一个位置。
  position 的最大值为 Buffer 的 capacity - 1,准确地说应该是 limit - 1。
 3、limit
  在写模式下,limit 表示能够向该 Buffer 写入多少数据,这时 limit 的值等于 Buffer 的 capacity。
  当切换到读模式时,limit 则表示可以从 Buffer 中读取的数据量。因此,当切换到读模式时,limit 的值会被设置为写模式时写到的那个位置。换句话说,你最多只能读取到你写的字节个数个的数据(limit 的值被设置为了写入数据的字节数,这个值正是写模式下的 position 的值)。

四、Buffer 的类型
 Java NIO 中有以下几种 Buffer 类型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

 这些类型的 Buffer 可以处理不同的数据类型,让我们可以通过处理 char、short、int、long、float 或者 double 来完成对 Buffer 中的字节数据的处理。MappedByteBuffer 稍微有一点特殊,会在后面讲解。

五、Buffer 的操作
 1、Buffer 的分配
  想得到一个 Buffer 对象首先得进行分配。每一个 Buffer 类都有一个 allocate() 方法,通过调用该方法即可得到一个 Buffer 对象,下面是一个 ByteBuffer 分配一个容量为 48 的 Buffer 的示例:

ByteBuffer buf = ByteBuffer.allocate(48);

  CharBuffer 分配 Buffer 的示例:

CharBuffer buf = CharBuffer.allocate(1024);

 2、向 Buffer 中写数据
  有两种方式:
   1️⃣从 Channel 中向 Buffer 中写数据

int bytesRead = inChannel.read(buf);

   2️⃣手动调用 Buffer 的 put() 方法向 Buffer 中写数据

buf.put(127); 

  Buffer 的各个实现类的 put() 方法有很多重载,可以采用很多不同的方式向 Buffer 中写数据。例如在写数据的时候指定 position,或者直接将一个字节数组写入 Buffer。
 3、flip()
  flip() 方法可以将 Buffer 从写模式切换到读模式,调用该方法时会将 limit 的值置为当前 position 的值,然后将 position 的值置为 0,源码如下:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

  也就是说,调用 flip() 之后的 position 标记的是将要读取数据的起始位置,limit 标记的则是 Buffer 中已经写入了多少个 byte 或者 char 等,也就是可以读取多少个 byte 或者 char 等。
 4、从 Buffer 中读数据
  有两种方式从 Buffer 中读取数据:
   1️⃣将 Buffer 中的数据读取到一个 Channel 中

int bytesWritten = inChannel.write(buf);

   2️⃣手动通过调用 Buffer 的 get() 方法

byte aByte = buf.get(); 

  Buffer 的各个实现类的 get() 方法也有很多重载,可以采用很多不同的方式从 Buffer 中读数据。例如在读数据的时候指定 position,或者直接从 Buffer 中读取一个字节数组。
 5、rewind()
  Buffer 的 rewind() 方法会将 position 的值置为 0,而 limit 的值不受影响,依然标记着 Buffer 中有多少个可读的元素,因此可以通过多次调用 rewind() 重复读取 Buffer 中的数据。
 6、clear() 和 compact()
  一旦完成了从 Buffer 读取数据的操作,就要让 Buffer 再次为写入数据做准备,也就是说 Buffer 是可重复读写的。可以通过调用 Buffer 的 clear() 或者 compact() 完成这个任务。
  如果调用了 clear(),会将 position 的值置为 0,limit 的值置为 capacity。此时虽然 Buffer 被“清空了”(相当于逻辑清除),但 Buffer 中的数据并没有被删除,只不过此时将会从 position 为 0 的位置开始写入数据。这样在调用 flip() 方法之前,是可以读取到之前写入的老数据的(前提是老数据尚未被覆盖)。在调用 clear() 方法时 Buffer 中那些尚未被读取的数据就相当于被遗忘了,因为此时你不知道哪些数据是读过的,哪些没有读过,也就是说你不知道应该从哪个 position 读起。
  如果 Buffer 中有尚未读取的数据,你过会要读取这些数据,但在这之前你不得不执行写入操作,就需要调用 compact() 而不是 clear() 了。compact() 方法会将所有未读取的数据拷贝到 Buffer 的起始位置,然后将 position 的值置为这些未读取数据的最后一位的后面的位置,limit 的值依然像 clear() 那样会被置为 capacity 的值,这样在再次写入数据的时候那些尚未被读取的数据不会被覆盖。可以结合 mark() 方法使用。
 7、mark() 和 reset()
  可以调用 Buffer 的 mark() 方法标记 Buffer 的当前 position 值,过后你可以通过调用 reset() 方法将 position 的值置为刚才标记的位置,示例如下:

public void testCompactWithMarkAndReset() {
	String str1 = "abcdef";
	String str2 = "123456";
	CharBuffer cb = CharBuffer.allocate(20);
	cb.put(str1);// 将str1写入cb
	cb.flip();
	char[] arr1 = new char[str1.length()];
	int index = str1.length() / 2;
	cb.get(arr1, 0, index);// 读取str1的一半到arr1
	cb.compact().mark();// 清理并mark下str1在cb中的终点位置
	cb.put(str2);// 写入其他数据
	int limit = cb.limit();// 记录下此时的limit值,用于知道最终的可读位置
	cb.reset().flip();// 恢复对str1的读取
	cb.get(arr1, index, cb.limit());// 继续读取str1
	for (int i = 0; i < arr1.length; i++)
		System.out.print(arr1[i]);
	cb.limit(limit);// 重置cb的limit值
	char[] arr2 = new char[str2.length()];
	System.out.println();
	cb.get(arr2);// 读取str2
	for (int i = 0; i < arr2.length; i++)
		System.out.print(arr2[i]);
}

  思考:为什么 Buffer 的获取 mark 值的 markValue() 方法要设计为 package-private 呢?导致 package 外无法获取到 mark 值,这样在 compact() 之后想读取后写入的数据就很麻烦,有可能是设计上的失误。
 8、equals() 和 compareTo()
  可以使用 equals() 和 compareTo() 比较两个 Buffer。
  ①两个 Buffer 相同(equal)有以下几个条件:和元素的顺序无关
   1️⃣它们是同一种 Buffer 类型(byte、char、int等)
   2️⃣它们剩余的元素个数相同
   3️⃣所有剩余的元素也都相同

String str1 = "abcdef";
String str2 = "abdcfe";
CharBuffer cb1 = CharBuffer.allocate(20);
CharBuffer cb2 = CharBuffer.allocate(20);
cb1.put(str1);
cb2.put(str2);
System.out.println(cb1.equals(cb2));// true

  equals() 方法只比较 Buffer 中的一部分,而不是比较里面的每一个元素。实际上,它只是比较 Buffer 中剩余的元素(即 position < limit 的那些未读取的元素)。
  ②对两个 Buffer 进行 compareTo() 比较时,也是仅比较剩余的元素,当满足如下比较规则时,一个 Buffer 会被认为比另一个小:
   1️⃣第一个不相等的元素小于另一个 Buffer 中对应的元素
   2️⃣所有元素通过 compareTo() 比较时都相等 ,但第一个 Buffer 比另一个 Buffer 的元素个数少,也就是元素较少的那个被认为是小的那一个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值