ByteBuffer使用揭秘

ByteBuffer是JavaNIO中的核心组件,用于读写数据。其内部结构包括position、limit和capacity等指针,通过flip()、clear()、rewind()等方法在读写模式间切换。文章通过实例展示了如何使用ByteBuffer进行数据的写入、标记、重置和读取,强调理解指针变化对于有效使用ByteBuffer至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结构介绍

ByteBuffer是NIO产生的时候带出来的一个对象,是Channel读写数据的缓冲区

在这里插入图片描述

我们在使用NIO的时候通常会看到这样一行代码:

 ByteBuffer allocate = ByteBuffer.allocate(1024);

它底层的实现是这样的,实际就是上图所标记的HeapByteBuffer

在这里插入图片描述

底层其实就是一个字节数组,但是有几个不同作用的指针,再我们执行完上面那行代码后,初始化的结构如下图所示:

在这里插入图片描述

生成一个长度为1024的数组以及4个指针,分别是:

  • position: 读写指针,在写的情况下,记录写数据的偏移量;在读的情况下,记录读数据的偏移量;
  • capacity: 数组大小的边界(就是数组容量,不会变)
  • limit: 读写边界指针,写数据的边界或者读数据的边界
  • mark: 标记指针,可以标记一个position的位置

重点:写或者读的空间都是 position——limit之间的空间

这样一看可能有点懵逼是吧,我们换个思路想,这个缓冲区分为读、写两种模式,像上图初始化后默认就是写模式,所以position到limit之间都是可写的空间(1024),每写入一个字节,position就会像前移动一个字节,假设我们写入 “Hello”,一共五个字节,那内部就是这样的:

此时:position变为了5
可写空间为:position——limit 之间  可以继续写,position会一直向前移动
可读空间为:position——limit 之间  虽然可以读,但是读取不到数据
这就可以称为写模式

在这里插入图片描述

上图还是写模式,如果想读取数据,那就需要切换到读模式,切换后结构如下:

此时:position变为了0 ,limit变为了5
可写空间为:position——limit 之间  可以写,但是会覆盖之前内容且不得超过limit
可读空间为:position——limit 之间  所以可以读取数据,每读一个字节,position向前移动一个字节
这就被称为读模式

在这里插入图片描述

好了,到这相信大家有了一个大概的了解了

常用方法说明

ByteBuffer.remaining()

获取position—limit之间还有多少空间,也就是返回两者差值

常用来获取可读数据的长度,或者可写数据的空间大小

源代码如下:

在这里插入图片描述

ByteBuffer.hasRemaining()

判断是否还可读 或者 是否还有空间可写 true:可写或未读完 false:反之

常用来判断数据是否完全读取、是否还能写入数据

源代码如下:

在这里插入图片描述

ByteBuffer.flip()

从写模式切换为读模式,实际就是移动指针

在写模式下,position指针会一直向前移动,但是我们想读取数据是在0——position之间的
所以我们需要将limit放到position的位置,position置为0,此时再读position-limit就是我们想要的数据啦
所以此操作可以称为写模式到读模式的切换

源代码如下:

在这里插入图片描述

ByteBuffer.clear()

重置指针,将指针恢复到初始化的情况

恢复到初始化的指针情况,目的是让你可以重新的写入
此时注意,此操作只是重置了指针并没有清除数据
如果缓冲区内有数据,重置了后去读还是可以读取数据的

源代码如下:

在这里插入图片描述

ByteBuffer.rewind()

重置position指针

这个重置与上面的重置不同,只重置了position指针,有两层语义,同样不会清除数据
写模式下:可以让你重新写
读模式下:可以让你重新读

而clear则是强制恢复到初始位置

源代码如下:

在这里插入图片描述

ByteBuffer.mark()与ByteBuffer.reset()

是不是感觉mark指针没用到?这个只是个标记指针

ByteBuffer.mark()源代码如下:

在这里插入图片描述

ByteBuffer.reset()源代码如下:

在这里插入图片描述

这两个方法一般搭配使用,场景就是position指针读取或者写入都会不断向前,要是中间有段内容想要重写怎么办?中间有段内容想要重新读取怎么办?所以我们打上一个标记点,之后方便重新回到这个标记点重新操作

  • mark():记录当前position的位置
  • reset(): 将position恢复到mark指针标记的位置

接着上面写入的图来,Mark后是这样的:

reset后不会清除数据,图示只是为了方便

在这里插入图片描述

get和put这种简单的就不说明了

实操演示

场景1:先写后读

// 初始化
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 写入数据
allocate.put("Hello".getBytes());
// 切换指针 然后读取
allocate.flip();
// 初始化一个和可读数据一样大小的数组
byte[] bytes = new byte[allocate.remaining()];
// 将数据读取到数组中
allocate.get(bytes);
System.out.println(new String(bytes));

输出结果:Hello

场景2:写→标记→写→重置标记→写→读

// 初始化
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 写入数据
allocate.put("Hello".getBytes());
// 标记
allocate.mark();
// 写入数据
allocate.put(" Hello".getBytes());
// 重置到标记点
allocate.reset();
// 再重新写,覆盖之前的错误数据
allocate.put(" World!".getBytes());
// 切换指针 然后读取
allocate.flip();
// 初始化一个和可读数据一样大小的数组
byte[] bytes = new byte[allocate.remaining()];
// 将数据读取到数组中
allocate.get(bytes);
System.out.println(new String(bytes));

输出:Hello World!

场景3:写→标记→写→重置标记→读

// 初始化
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 写入数据
allocate.put("Hello".getBytes());
// 标记
allocate.mark();
// 写入数据
allocate.put(" World!".getBytes());
// 重置到标记点
allocate.reset();
// 切换指针 然后读取
allocate.flip();
// 初始化一个和可读数据一样大小的数组
byte[] bytes = new byte[allocate.remaining()];
// 将数据读取到数组中
allocate.get(bytes);
System.out.println(new String(bytes));

输出:Hello 因为重置到标记点了,标记点后的数据就读取不了

场景4:写→读→判断数据是否完全读取→没读完继续读

// 初始化
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 写入数据
allocate.put("Hello World!".getBytes());
// 切换指针 然后读取
allocate.flip();
// 初始化一个和可读数据一样大小的数组
byte[] bytes = new byte[5];
// 将数据读取到数组中
allocate.get(bytes);

String data=new String(bytes);
// 才读了5个字节肯定没读完
while (allocate.hasRemaining()){
    System.out.println("没读完继续读");
    byte[] bytes1 = new byte[allocate.remaining()];
    allocate.get(bytes1);
    data+=new String(bytes1);
}
System.out.println(data);

输出:
没读完继续读
Hello World!

场景5:写→重复读

// 初始化
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 写入数据
allocate.put("Hello World!".getBytes());
// 切换指针 然后读取
allocate.flip();
// 初始化一个和可读数据一样大小的数组
byte[] bytes = new byte[allocate.remaining()];
//重复读三次
for (int i = 0; i < 3; i++) {
    // 将数据读取到数组中
    allocate.get(bytes);
    System.out.println(new String(bytes));
    // 重置position指针到0
    allocate.rewind();
}

输出:
Hello World!
Hello World!
Hello World!

总结

可以看到ByteBuffer底层结构和逻辑都是比较简单的,所有的操作也都是和指针有关,只要把指针这块的逻辑搞清楚了,就算使用时忘记了,直接看一下源码也就OK了

<think>好的,我现在需要帮助用户了解如何使用Java NIOByteBuffer类。用户明确要求教程、示例和详解,所以我得先理清ByteBuffer的基本概念和核心组件。首先,我记得ByteBufferJava NIO中的一个关键类,用于高效处理字节数据。根据引用[2],Java NIO提供了非阻塞、高效的I/O操作,而ByteBuffer是其核心之一。 首先,我应该从ByteBuffer的创建方法开始。用户可能需要知道如何分配一个缓冲区,所以需要提到allocateallocateDirect的区别。引用[3]提到需要弄清楚position、limit、capacity这些变量,所以我得详细解释这些属性。 接下来是缓冲区的读写操作。用户可能需要示例代码来演示如何写入和读取数据。比如put()和get()方法的不同用法,包括绝对定位和相对定位。引用[1]提到put方法有很多版本,可以写入不同数据,这里需要具体说明如何操作。 然后是关于flip()、rewind()、clear()等方法的作用,这些是切换读写模式的关键步骤,容易混淆,必须详细解释。例如,flip()方法用于切换为读模式,调整limit和position的位置,而clear()则重置缓冲区但不清数据,这些都需要明确说明。 还需要涵盖标记(mark)和重置(reset)的用法,以及如何复制缓冲区,比如slice()和duplicate()方法。引用[4]提到的arrayOffset()方法也很重要,如果缓冲区有后备数组的话,用户可能需要知道如何访问底层数组的偏移量。 此外,应该提供一些完整的示例代码,展示从创建缓冲区到读写数据的完整流程,帮助用户理解各个步骤如何衔接。比如,写入字符串到ByteBuffer,然后读取出来,这样的例子比较直观。 最后,要确保回答结构清晰,分步骤讲解,并附上相关问题供用户进一步学习。需要检查所有LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,但在这个回答中可能公式较少,主要关注代码块和引用标识。同时引用提供的四个参考文献,适当在相关部分添加引用标记,比如在提到NIO核心概念时引用[^2],在讲解position等变量时引用[^3]等。 总结一下,回答的结构应该是:概述、创建缓冲区、核心属性、读写操作、模式切换、其他方法、示例代码、常见问题,最后是相关问题。这样用户能够逐步学习ByteBuffer使用方法,并通过示例加深理解。</think>### Java NIO ByteBuffer 使用教程详解 #### 1. ByteBuffer 概述 Java NIO 的 $ByteBuffer$ 类是处理字节数据的核心工具,用于高效读写二进制数据(如文件、网络传输)。它通过直接内存访问和缓冲区管理优化 I/O 性能[^2]。 --- #### 2. 创建 ByteBuffer ```java // 分配堆内存缓冲区(JVM 堆内) ByteBuffer heapBuffer = ByteBuffer.allocate(1024); // 分配直接内存缓冲区(操作系统堆外,减少拷贝开销) ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); // 包装现有数组(数据修改会同步到数组) byte[] arr = new byte[1024]; ByteBuffer wrappedBuffer = ByteBuffer.wrap(arr); ``` --- #### 3. 核心属性与关系 - **capacity**:缓冲区的最大容量(固定值) - **position**:当前读写位置(初始为 0) - **limit**:可读/写的最大位置(初始等于 capacity) - **mark**:临时标记位置(通过 `mark()` 设置) 转换关系如图: ```plaintext 0 ≤ mark ≤ position ≤ limit ≤ capacity ``` --- #### 4. 读写操作 **写入数据**(写模式): ```java byte[] data = "Hello".getBytes(); heapBuffer.put(data); // 相对写入(position 自动后移) heapBuffer.put(2, (byte)0x41); // 绝对写入(指定索引位置) ``` **读取数据**(读模式): ```java heapBuffer.flip(); // 切换为读模式(limit=原position,position=0) while (heapBuffer.hasRemaining()) { byte b = heapBuffer.get(); // 相对读取 } ``` --- #### 5. 模式切换方法 - **`flip()`**:写模式 → 读模式 $$limit = position,\quad position = 0$$ - **`clear()`**:重置缓冲区(不清数据) $$position = 0,\quad limit = capacity$$ - **`rewind()`**:重新读取数据 $$position = 0$$ --- #### 6. 其他重要方法 - **`slice()`**:创建共享数据的子缓冲区 - **`duplicate()`**:创建完全副本(共享底层数据) - **`arrayOffset()`**:获取缓冲区在底层数组的偏移量[^4] ```java if (heapBuffer.hasArray()) { int offset = heapBuffer.arrayOffset() + heapBuffer.position(); } ``` --- #### 7. 完整示例 ```java // 创建并写入数据 ByteBuffer buffer = ByteBuffer.allocate(128); buffer.put("NIO ByteBuffer".getBytes(StandardCharsets.UTF_8)); // 切换读模式 buffer.flip(); // 读取数据到数组 byte[] readData = new byte[buffer.remaining()]; buffer.get(readData); System.out.println(new String(readData)); // 输出 "NIO ByteBuffer" ``` --- #### 8. 常见问题 **Q:堆缓冲区与直接缓冲区的区别?** A:堆缓冲区由 JVM 管理,直接缓冲区通过操作系统本地内存分配(减少拷贝次数,适合高频 I/O 操作)。 **Q:如何避免缓冲区溢出?** A:操作前检查剩余空间:`if (buffer.remaining() >= neededSize) { ... }` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值