Java ByteBuffer:如何使用 flip() 和 compact()

原创
2021/07/01 08:15
阅读数 164

在本文中,我将使用一个示例向您展示 JavaByteBuffer是如何工作的,以及 Methodenflip()compact()它的作用。

文章回答了以下问题:

  • 什么是 a ByteBuffer,你需要它做什么?
  • 你如何创建一个ByteBuffer
  • 什么价值观positionlimit以及capacity是什么意思?
  • 我如何写入ByteBuffer,如何从中读取?
  • 究竟是做什么的方法flip()compact()做什么?
内容 
1 什么是 ByteBuffer,你需要它做什么?
2 如何创建一个ByteBuffer
3 ByteBuffer 位置、限制和容量
4 ByteBuffer 读写周期
4.1 使用 put() 写入 ByteBuffer
4.2 使用 Buffer.flip() 切换到读取模式
4.3 使用 get() 从 ByteBuffer 中读取
4.4 切换到写入模式 - 如何不这样做
4.5 使用 Buffer.compact() 切换到写入模式
4.6 下一个循环
5 总结

什么是 ByteBuffer,你需要它做什么?

您需要ByteBuffer使用所谓的Channel. 这篇文章主要是关于它ByteBuffer本身。要了解如何阅读和写文件ByteBufferFileChannel阅读这篇文章。

AByteBuffer是字节数组的包装器,并提供方便地写入和读取字节数组的方法。该ByteBuffer内部存储的读/写位置和所谓的“极限”。

您可以在以下示例中逐步了解这到底意味着什么。

您可以在我的GitHub Repository 中找到为本文编写的代码。

如何创建一个字节缓冲区

首先,您必须创建ByteBuffer具有给定大小(“容量”)的一个。为此,有两种方法:

  • ByteBuffer.allocate(int capacity)
  • ByteBuffer.allocateDirect(int capacity)

该参数capacity以字节为单位指定缓冲区的大小。

allocate()方法在 Java 堆内存中创建缓冲区,垃圾收集器将在使用后将其删除。

allocateDirect(),另一方面,在本机内存中创建缓冲区,即在堆外。本机内存的优点是可以更快地执行读取和写入操作。原因是相应的操作系统操作可以直接访问这块内存区域,而不必先在Java堆和操作系统之间进行数据交换。这种方法的缺点是较高的分配和解除分配成本。

我们创建一个ByteBuffer大小为 1,000 字节的文件,如下所示:

 var buffer = ByteBuffer.allocate(1000); 

然后我们看看缓冲区的指标 – positionlimitcapacity

 System.out.printf("position = %4d, limit = %4d, capacity = %4d%n", buffer.position(), buffer.limit(), buffer.capacity()); 

(由于我们将在整个示例中重复打印这些指标,因此我们立即将System.out.println()命令提取到一个printMetrics(buffer)方法中。)

我们看到以下输出:

position = 0, limit = 1000, capacity = 1000

这是一个图形表示,以便您可以更好地想象缓冲区。浅黄色区域是空的,可以随后填充。

ByteBuffer mit 位置 = 0,限制 = 1000,容量 = 1000

ByteBuffer 位置、限制和容量

显示指标的含义:

  • position是读/写位置。对于新缓冲区,它始终为 0。
  • limit有两个含义: 当我们写入缓冲区时,limit指示我们可以写入的位置。当我们从缓冲区读取时,limit指示缓冲区包含数据的位置。最初, aByteBuffer始终处于写入模式,并且limit等于capacity- 我们可以将空缓冲区填充到最后。
  • capacity指示缓冲区的大小。它的值 1,000 对应于我们传递给该allocate()方法的 1,000 个字节。它在缓冲区的生命周期内不会改变。

ByteBuffer 读写周期

使用 put() 写入 ByteBuffer

为了写入ByteBuffer,有多种put()方法可以将单个字节、字节数组或其他原始类型(如 char、double、float、int、long、short)写入缓冲区。首先,我们将值 1 的 100 倍写入缓冲区,然后我们再次查看缓冲区指标:

for (int i = 0; i < 100; i++) {
  buffer.put((byte) 1);
}

printMetrics(buffer);

运行程序后,我们看到以下输出:

position = 100, limit = 1000, capacity = 1000

该位置已向右移动 100 个字节;缓冲区现在如下所示:

ByteBuffer,位置 = 100,限制 = 1000,容量 = 1000

接下来,我们在缓冲区中写入 200 次 2。这次我们使用不同的方法:我们首先填充一个字节数组并将其复制到缓冲区中。最后,我们再次打印指标:

byte[] twos = new byte[200];
Arrays.fill(twos, (byte) 2);
buffer.put(twos);

printMetrics(buffer);

现在我们看到:

position = 300, limit = 1000, capacity = 1000

该位置又向右移动了 200 个字节;缓冲区看起来像这样:

ByteBuffer,位置 = 300,限制 = 1000,容量 = 1000

使用 Buffer.flip() 切换到读取模式

对于从缓冲区读取,有相应的get()方法。例如,当使用Channel.write(buffer).

由于position不仅指示写入位置,还指示读取位置,因此我们必须position重新设置为 0。

同时,我们设置limit为 300 表示最多可以从缓冲区中读取 300 个字节。

在程序代码中,我们这样做:

buffer.limit(buffer.position());
buffer.position(0);

由于每次从写入模式切换到读取模式时都需要这两行,因此有一种方法可以做到:

buffer.flip();

printMetrics()现在调用显示以下值:

position = 0, limit = 300, capacity = 1000

于是位置指针又回到了缓冲区的开头,limit指向了填充区域的结尾:

ByteBuffer,位置 = 0,限制 = 300,容量 = 1000

使用 get() 从 ByteBuffer 读取

假设我们要写入的通道当前只能占用 300 个字节中的 200 个。我们可以通过为该ByteBuffer.get()方法提供一个 200 字节大小的字节数组来模拟这一点,缓冲区应在其中写入其数据:

buffer.get(new byte[200]);

printMetrics() 现在显示以下内容:

position = 200, limit = 300, capacity = 1000

读取位置已经向右移动了 200 个字节——即到了已经读取数据的末尾,也就是我们还需要读取的数据的开始位置:

ByteBuffer,位置 = 200,限制 = 300,容量 = 1000

切换到写入模式 - 如何这样做

现在要写回缓冲区,您可能会犯以下错误:您设置position了数据的末尾,即 300,然后limit又设置为 1000,这使我们回到了写完 1 和 2 之后的状态:

ByteBuffer,位置 = 300,限制 = 1000,容量 = 1000

假设我们现在要向缓冲区写入 300 个字节。缓冲区将如下所示:

ByteBuffer,位置 = 600,限制 = 1000,容量 = 1000

如果我们现在使用flip()切换回读取模式,position将回到 0:

ByteBuffer,位置 = 0,限制 = 600,容量 = 1000

但是,现在我们将再次读取我们已经读取的前 200 个字节。

因此,这种方法是错误的。以下部分说明如何正确执行此操作。

使用 Buffer.compact() 切换到写入模式

相反,当切换到写入模式时,我们必须按以下步骤进行:

  • 我们计算剩余字节数:remaining = limit - position在示例中,结果为 100。
  • 我们将剩余的字节移到缓冲区的开头。
  • 我们将写入位置设置为左移字节的末尾,在示例中为 100。
  • 我们设置limit到缓冲区的末尾。

ByteBuffer 还为此提供了一个方便的方法:

 buffer.compact(); 

调用后compact()printMetrics()打印以下内容:

position = 100, limit = 1000, capacity = 1000

在图中,该compact()过程如下所示:

ByteBuffer,位置 = 100,限制 = 1000,容量 = 1000

下一个循环

现在我们可以将接下来的 300 个字节写入缓冲区:

byte[] threes = new byte[300];
Arrays.fill(threes, (byte) 3);
buffer.put(threes);

printMetrics() 现在显示以下值:

position = 400, limit = 1000, capacity = 1000

写完三个之后,position向右移动了 300 个字节:

ByteBuffer,位置 = 400,限制 = 1000,容量 = 1000

现在我们可以使用以下命令轻松切换回阅读模式flip()

 buffer.flip(); 

最后一次调用printMetrics()打印以下值:

position = 0, limit = 400, capacity = 1000

读取位置位于缓冲区的开头,该compact()方法将剩余的 100 个二进制移至该位置。所以我们现在可以准确地在我们之前停止的位置继续阅读。

ByteBuffer,位置 = 0,限制 = 400,容量 = 1000

概括

本文介绍了Java的功能ByteBuffer和它flip()compact()方法。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部