在本文中,我将使用一个示例向您展示 JavaByteBuffer
是如何工作的,以及 方法flip()
和compact()
它的作用。
文章回答了以下问题:
- 什么是 一个
ByteBuffer
,你需要它做什么? - 你如何创建一个
ByteBuffer
? position
,limit
以及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
本身。要了解如何阅读和写文件ByteBuffer
和FileChannel
阅读这篇文章。
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);
然后我们看看缓冲区的指标 – position
,limit
和capacity
:
System.out.printf("position = M, limit = M, capacity = M%n", buffer.position(), buffer.limit(), buffer.capacity());
(由于我们将在整个示例中重复打印这些指标,因此我们立即将System.out.println()
命令提取到一个printMetrics(buffer)
方法中。)
我们看到以下输出:
position = 0, limit = 1000, capacity = 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 个字节;缓冲区现在如下所示:
接下来,我们在缓冲区中写入 200 次 2。这次我们使用不同的方法:我们首先填充一个字节数组并将其复制到缓冲区中。最后,我们再次打印指标:
代码语言:javascript复制byte[] twos = new byte[200];
Arrays.fill(twos, (byte) 2);
buffer.put(twos);
printMetrics(buffer);
现在我们看到:
position = 300, limit = 1000, capacity = 1000
该位置又向右移动了 200 个字节;缓冲区看起来像这样:
使用 Buffer.flip() 切换到读取模式
对于从缓冲区读取,有相应的get()
方法。例如,当使用Channel.write(buffer)
.
由于position
不仅指示写入位置,还指示读取位置,因此我们必须position
重新设置为 0。
同时,我们设置limit
为 300 表示最多可以从缓冲区中读取 300 个字节。
在程序代码中,我们这样做:
代码语言:javascript复制buffer.limit(buffer.position());
buffer.position(0);
由于每次从写入模式切换到读取模式时都需要这两行,因此有一种方法可以做到:
代码语言:javascript复制buffer.flip();
printMetrics()
现在调用显示以下值:
position = 0, limit = 300, capacity = 1000
于是位置指针又回到了缓冲区的开头,limit
指向了填充区域的结尾:
使用 get() 从 ByteBuffer 读取
假设我们要写入的通道当前只能占用 300 个字节中的 200 个。我们可以通过为该ByteBuffer.get()
方法提供一个 200 字节大小的字节数组来模拟这一点,缓冲区应在其中写入其数据:
buffer.get(new byte[200]);
printMetrics()
现在显示以下内容:
position = 200, limit = 300, capacity = 1000
读取位置已经向右移动了 200 个字节——即到了已经读取数据的末尾,也就是我们还需要读取的数据的开始位置:
切换到写入模式 - 如何不这样做
现在要写回缓冲区,您可能会犯以下错误:您设置position
了数据的末尾,即 300,然后limit
又设置为 1000,这使我们回到了写完 1 和 2 之后的状态:
假设我们现在要向缓冲区写入 300 个字节。缓冲区将如下所示:
如果我们现在使用flip()
切换回读取模式,position
将回到 0:
但是,现在我们将再次读取我们已经读取的前 200 个字节。
因此,这种方法是错误的。以下部分说明如何正确执行此操作。
使用 Buffer.compact() 切换到写入模式
相反,当切换到写入模式时,我们必须按以下步骤进行:
- 我们计算剩余字节数:
remaining = limit - position
在示例中,结果为 100。 - 我们将剩余的字节移到缓冲区的开头。
- 我们将写入位置设置为左移字节的末尾,在示例中为 100。
- 我们设置
limit
到缓冲区的末尾。
ByteBuffer
还为此提供了一个方便的方法:
buffer.compact();
调用后compact()
,printMetrics()
打印以下内容:
position = 100, limit = 1000, capacity = 1000
在图中,该compact()
过程如下所示:
下一个循环
现在我们可以将接下来的 300 个字节写入缓冲区:
代码语言:javascript复制byte[] threes = new byte[300];
Arrays.fill(threes, (byte) 3);
buffer.put(threes);
printMetrics()
现在显示以下值:
position = 400, limit = 1000, capacity = 1000
写完三个之后,position
向右移动了 300 个字节:
现在我们可以使用以下命令轻松切换回阅读模式flip()
:
buffer.flip();
最后一次调用printMetrics()
打印以下值:
position = 0, limit = 400, capacity = 1000
读取位置位于缓冲区的开头,该compact()
方法将剩余的 100 个二进制移至该位置。所以我们现在可以准确地在我们之前停止的位置继续阅读。
概括
本文介绍了Java的功能ByteBuffer
和它flip()
与compact()
方法。