ByteBuffer详解

ByteBuffer简介

Buffer 类是构建 Java NIO 的基础,其中ByteBuffer 类是最Buffer子类中最受欢迎的。 这是因为字节类型是最通用的类型。 例如,我们可以在 JVM 中使用字节来组成其他非布尔基元类型。 另外,我们可以使用字节在 JVM 和外部 I/O 设备之间传输数据。

直接缓冲区与非直接缓冲区

ByteBuffer可以是直接缓冲区,还可以是非直接缓冲区,这两种缓冲区的差别:

内存分配

  • 非直接缓冲区(Heap Buffer): 使用 ByteBuffer.allocate(size) 创建的缓冲区,其数据存储在Java堆内存中。这意味着数据的存取需要经过JVM的内存管理,可能会涉及额外的内存复制(如从堆到内核空间)。
  • 直接缓冲区(Direct Buffer): 使用 ByteBuffer.allocateDirect(size) 创建的缓冲区,其数据直接存储在操作系统的非易失性内存中,绕过了Java堆。这减少了Java堆与操作系统之间的数据拷贝,提高了效率,特别是在处理大块数据或进行系统调用时。

性能

  • 直接缓冲区通常在处理大量数据或进行低级别I/O(如文件读写或网络通信)时性能更好,因为减少了数据在用户空间和内核空间之间复制的开销。
  • 非直接缓冲区在小规模的数据操作中可能更快,因为它们避免了内存映射的开销,但对大块数据操作可能较慢。

垃圾回收

  • 非直接缓冲区遵循Java的垃圾收集机制,当不再引用时会被自动释放。
  • 直接缓冲区不占用堆内存,因此不受Java堆大小的限制。但是,它们的生命周期管理更复杂,因为它们不会被垃圾收集器自动回收,除非没有其他强引用指向它们。以下是直接缓冲回收的机制:

引用计数:

Java NIO库为直接缓冲区维护了一个内部的引用计数。当创建一个直接缓冲区时,它的引用计数为1。每当有新的引用指向这个缓冲区时,计数增加;当引用失效时,计数减少。

弱引用:

直接缓冲区通常与一个Cleaner对象相关联,这是一个特殊的类,它实现了Cleaner接口。这个Cleaner对象是一个弱引用,意味着即使有Cleaner存在,如果直接缓冲区没有其他强引用,它仍然可以被垃圾收集器回收。

清理方法:

当缓冲区的引用计数降为0时,Cleaner对象的clean()方法会被调用。这个方法通常会释放操作系统级别的资源,但具体的实现取决于平台和JVM的实现。在某些情况下,这可能涉及到释放内存映射文件或关闭文件通道。

非标准垃圾回收:

直接缓冲区的垃圾回收不是Java标准垃圾收集机制的一部分,而是依赖于特定JVM的实现。这意味着回收过程可能不是立即的,也不一定在Java的垃圾收集停顿期间发生。

内存泄漏风险:

由于直接缓冲区的回收不完全依赖于Java的垃圾收集器,如果程序中存在未正确管理的直接缓冲区引用,可能导致内存泄漏,因为操作系统级别的内存可能无法被释放。

显式释放:

为了避免内存泄漏,开发者可以使用Cleaner对象显式地尝试释放缓冲区。例如,通过buffer.cleaner().clean(),但这样做并不是总是安全的,因为它可能会抛出异常,并且在某些情况下可能导致不一致的状态。

资源消耗

  • 直接缓冲区可能会消耗更多的系统资源,因为它们直接在物理内存中分配空间,且可能无法有效利用Java的内存压缩优化。
  • 非直接缓冲区则更容易与其他堆对象共享内存管理策略。

适用场景

  • 对于频繁的系统调用,如文件读写或网络通信,使用直接缓冲区通常更合适。
  • 对于小规模的内存操作或不涉及系统调用的情况,非直接缓冲区可能是更优的选择,因为它们的内存分配和管理更为简单。

ByteBuffer的操作详解

4个关键标记位

标记位

说明

mark

标记位,用于记录position的位置,初始值为-1

position

表示下一次读写操作将要发生的位置,默认初始值为0

limit

限制标志着在当前模式下(读或写)可以访问的字节的最大范围.

capacity

容量是 ByteBuffer 可以存储的最大字节数,一旦创建,容量是固定的。

以上4个标记位会随着ByteBuffer不同的操作进行更新,下面内容就用实例说明这些标志位的变化情况。

创建ByteBuffer

可以通过工具类ByteBuffer的allocate(size)或allocateDirect(size)创建一个Bytebuffer对象。

例如:创建一个容量为10的ByteBuffer,4个标志位的位置如下图,其中limit和capacity的大小都为初始化容量。

写数据

可以通过ByteBuffer提供的put方法往ByteBuffer写入数据,每次写入数据时position增加上新增数据的长度,例如写入5个字节的数据后,4个标志位的变化如下:

切换到读模式

flip方法将缓冲区从写模式切换到读模式,将limi设置为当前position的位置,position重置为0,限定了当前的可读取的数据范围。执行该方法后,4个标志位变化如下:

读取数据

get方法可以从缓冲区里面读取数据,get操作后,position增加上读取数据的长度,例如读取了1个字节的数据后,4个标志位的变化如下:

mark操作

mark操作用于记录当前position的位置,方便后续重新回到当前的位置读取数据。如下是mark操作后,4个标志位的变化如下:

执行mark之前,再读取2个字节后,4个标志为的变化如下:

reset操作

reset操作将缓冲区的position重置为最近一次mark标记的位置。如下是reset操作后,4个标志位的变化如下:

切换到写模式

方法一:clear操作可以将position重置为0,limit重置为capacity,但没有清除缓冲区中之前已写入的数据,如果要接着写入数据,需要重置一下position的位置,否则就是从0开始写入数据,覆盖之前的数据。如下clear操作之后4个标志为的变化:

方法二:compact操作将所有未读数据移到缓冲区的开始,并将position设置为当前未读数据的末尾加1,limit被设置为capacity。如下compact操作之后4个标志为的变化:

slice操作

slice()操作返回一个与原ByteBuffer共享相同底层缓冲区的新ByteBuffer。新缓冲区的容量是原缓冲区剩余容量,其position位置为0,limit和capacity为原缓冲区limit减去原缓冲区的position。例如在前文读取1个字节数据之后,执行slice()操作,新缓冲区的4个标志为的值为:

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ByteBuffer 是 Java 中用于处理二进制数据的缓冲区,它可以在缓冲区中存储不同型的数据,如 byte、short、int、long、float、double 等。使用 ByteBuffer 可以方便地进行数据的读取和写入,并且可以设置字节序(Byte Order)来保证数据在不同机器上的正确性。 ByteBuffer 的常用方法包括: 1. allocate(int capacity):分配一个容量为 capacity 的 ByteBuffer。 2. put(byte[] src):将一个字节数组写入到 ByteBuffer 中。 3. get(byte[] dest):将 ByteBuffer 中的数据读取到一个字节数组中。 4. flip():将读写模式切换,将 limit 设置为当前位置,将 position 设置为 0。 5. rewind():将 position 设置为 0,使得数据可以重新读取。 6. clear():清空 ByteBuffer,将 position 设置为 0,将 limit 设置为 capacity。 例如,下面是一个使用 ByteBuffer 写入和读取数据的示例代码: ```java // 创建一个容量为 10 的 ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(10); // 写入数据 buffer.putInt(123); buffer.putDouble(3.14); buffer.putChar('A'); // 切换到读模式 buffer.flip(); // 读取数据 int intValue = buffer.getInt(); double doubleValue = buffer.getDouble(); char charValue = buffer.getChar(); System.out.println(intValue); // 输出 123 System.out.println(doubleValue); // 输出 3.14 System.out.println(charValue); // 输出 A ``` 需要注意的是,ByteBuffer 中的 position、limit 和 capacity 属性的含义如下: 1. position:当前读写位置。 2. limit:缓冲区的限制,即当前可以读写的最大位置。 3. capacity:缓冲区的容量,即最多可以存储多少字节数据。 在写入数据时,position 会自动向前移动,而 limit 和 capacity 不会变化;在读取数据时,position 和 limit 会随着读取的数据量自动向前移动。如果需要重新读取数据,可以使用 rewind() 方法将 position 设置为 0,如果需要清空缓冲区,可以使用 clear() 方法将 position 设置为 0,limit 设置为 capacity。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值