Java NIO ByteBuffer 详解

什么是 ByteBuffer

ByteBuffer 是 Buffer 的一个具体实现,专门用于存储和操作字节数据。它提供了高效的、基于内存的 I/O 数据处理方式。

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

类关系图

ByteBuffer 是 NIO 里用得最多的 Buffer,它包含两个实现方式:

  1. 堆缓冲区 HeapByteBuffer 是基于堆的实现,使用 JVM 的堆内存,读写操作效率低,会受到 GC 影响。
  2. 直接缓冲区 MappedByteBuffer(DirectByteBuffer)使用 OS 的内存,读写操作效率高,不会受到 GC 影响。但不主动析构,会造成内存的泄露。

这里扩展一下什么是内存泄露和内存溢出:

内存溢出:实际的数据量已经超过了当前机器的实际物理内存,比如数据量 2.5 GB,但实际物理内存只有 2 GB,将全部数据量全部读取到内存中去放不下那多出来的 0.5 GB,这就叫做溢出(OutOfMemoryException)。

内存泄露:实际的数据量虽然小于当前机器的实际物理内存,但还没读取完内存竟然不够用了,比如数据量 2 GB,实际物理内存也是 2 GB,但只读取了 1.8 GB 内存就不够了。造成内存泄露的原因大概两点:没有主动析构释放内存、内存碎片。

直接缓冲区与非直接缓冲区的区别?
区别维度非直接缓冲区(Heap Buffer)直接缓冲区(Direct Buffer)
创建方式使用 ByteBuffer.allocate(size) 创建的缓冲区使用 ByteBuffer.allocateDirect(size) 创建的缓冲区
内存分配其数据存储在 Java 堆内存中。这意味着数据的存取需要经过 JVM 的内存管理,可能会涉及额外的内存复制(如从堆到内核空间)其数据直接存储在操作系统的非易失性内存中,绕过了 Java 堆。这减少了 Java 堆与操作系统之间的数据拷贝,提高了效率,特别是在处理大块数据或进行系统调用时
性能角度在小规模的数据操作中可能更快,因为它们避免了内存映射的开销,但对大块数据操作可能较慢通常在处理大量数据或进行低级别 I/O(如文件读写或网络通信)时性能更好,因为减少了数据在用户空间和内核空间之间复制的开销
垃圾回收遵循 Java 的垃圾收集机制,当不再引用时会被自动释放不占用堆内存,因此不受 Java 堆大小的限制。但是,它们的生命周期管理更复杂,因为它们不会被垃圾收集器自动回收,除非没有其他强引用指向它们。

创建 ByteBuffer 的方法

包装现有数组

// 使用现有的 byte 数组创建缓冲区
byte[] byteArray = new byte[1024];
ByteBuffer wrappedBuffer = ByteBuffer.wrap(byteArray);

分配缓冲区

JDK 提供的 ByteBuffer 一旦分配空间,不可以动态调整大小,但是 Netty 对它进行了改进,支持了动态调整。

正是由于不能动态的扩充,如果添加的数据超过了容量,则会抛出 BufferOverflowException 异常。

// 分配一个容量为 1024 字节的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);

分配直接缓冲区

// 分配一个直接缓冲区,性能较高
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

通过字符串集和字符串创建

// 获取字符集,假设使用 UTF-8 编码
Charset charset = StandardCharsets.UTF_8;
ByteBuffer encode = charset.encode("Hello, World!");

核心结构

ByteBuffer 的核心结构围绕三个重要属性构建,这些属性决定了缓冲区的行为和状态:

Capacity(容量)

缓冲区的最大存储数据量,初始化时即固定,类似于数组的 Size。不可改变,决定了该缓冲区最多能容纳的字节数。

使用场景:确保缓冲区的容量足够大,以存储需要处理的数据。

ByteBuffer buffer = ByteBuffer.allocate(1024); // 容量为 1024 字节
System.out.println(buffer.capacity()); // 输出 1024

Position(当前位置)

当前读/写操作的索引,指向缓冲区中下一个要读或写的位置。Buffer 当前缓存的下标,在读取操作时记录读到了那个位置,在写操作时记录写到了那个位置。从 0 开始,每读取一次,下标 +1。读/写数据时会自动变化,范围为 [0, limit]

使用场景:追踪数据操作的进度。

buffer.put((byte) 10); // 写入一个字节数据
System.out.println(buffer.position()); // 输出 1,表示当前位置为索引 1

Limit(限制)

当前读/写操作的限制索引,定义了可读或可写的范围。在读操作时,设置了你能读多少字节的数据,在写操作时,设置了你还能写多少字节的数据。写模式下,limit 等于 capacity;读模式下,limit 表示可读数据的终点。

使用场景:切换读/写模式时更新 limit,确保数据操作的边界清晰。

buffer.flip(); // 切换到读模式
System.out.println(buffer.limit()); // 输出 1,表示可读数据的终点

内部关系

  • 写模式:默认 position 从 0 开始,limit 等于 capacity。
  • 读模式:通过调用 flip() 方法将缓冲区切换到读模式,此时 limit 被设置为当前 position 值,而 position 被重置为 0。

所谓的读写模式,本质上就是这几个状态的变化。Position 和 Limit 联合决定了 Buffer 的读写数据区域。

这里有个坑,如果连续设置两次读模式,那么就会读不到数据:

ByteBuffer buffer = ByteBuffer.allocate(6);
buffer.put(new byte[]{11, 22, 33});
// 容量 = 6 当前位置 = 3 限制 = 6
System.out.printf(format, buffer.capacity(), buffer.position(), buffer.limit());buffer.flip();
// 容量 = 6 当前位置 = 0 限制 = 3
System.out.printf(format, buffer.capacity(), buffer.position(), buffer.limit());
buffer.flip();
// 容量 = 6 当前位置 = 0 限制 = 0
System.out.printf(format, buffer.capacity(), buffer.position(), buffer.limit());while (buffer.hasRemaining()) {System.out.println(buffer.get());
}

这个可以看到,两次 flip 之后,limit 是有变化的,第二次执行后 limit 直接就是 0 了,所以自然而然就读不到数据了,我们看一下源码:

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

注意第一行,每次调用 flip将都会将当前的 position 设置为 limit,然后将 position 设置为 0,当第二次执行 flip 之后,limit 就赋值了第一次赋值为 0 的 position。

数据操作 API

写数据:写模式,创建后、clear、compact

每次写入 position 自动递增。

  • put(byte b):将单个字节写入缓冲区。
  • put(byte[] src):将字节数组写入缓冲区。
  • channel.read(buffer):从通道向缓冲区写入。

读数据:读模式,调用 flip

每次读取 position 自动递增。

  • get():读取当前 position 的字节。
  • get(byte[] dst):从缓冲区读取多个字节。
  • get(int index):方法,获取特定 position 上的数据,但是不会对 position 的位置产生影响。
  • rewind():可以将 postion 重置成 0,用于复读数据。
  • mark() & reset() :通过 mark 方法进行标记(position),通过 reset 方法跳回标记,从新执行。
  • remaining(): 返回当前缓冲区剩余可读的字节数(即 limit - position)。

字符串操作

字符串存储到 Buffer 中

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("Hello".getBytes());// 自动把 ByteBuffer 设置成读模式,且不能手工调用 flip 方法。
ByteBuffer buffer = Charset.forName("UTF-8").encode("Hello");
// 自动把 ByteBuffer 设置成读模式,且不能手工调用 flip 方法。
ByteBuffer buffer = StandardCharsets.UTF_8.encode("Hello");
// 自动把 ByteBuffer 设置成读模式,且不能手工调用 flip 方法。
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());

Buffer 中的数据转换成字符串

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("Hello".getBytes());
CharBuffer result = StandardCharsets.UTF_8.decode(buffer);
System.out.println("result.toString() = " + result.toString());

粘包与半包

半包问题指的是发送方在一次写操作中发送的数据大小超过了接收方的接收缓冲区大小,导致数据被拆分成多个部分,在接收方到达时只接收到其中的一部分,剩下的部分需要在后续的读取操作中继续接收。

粘包问题与半包相反,指的是多个小的数据包被TCP层合并成一个大的数据包传送给接收方,接收方无法分辨出不同数据包的边界,导致接收到的是一条合并的、粘在一起的数据包。

这里可以通过在每条消息的末尾添加一个分隔符(如 \n 或其他字符)来标识消息的边界。接收方通过读取直到分隔符的内容来识别完整的消息。

public class CompactDemo {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(50);buffer.put("Hi XUE WEI\nl love y".getBytes());doLineSplit(buffer);buffer.put("ou\nDo you like me?\n".getBytes());doLineSplit(buffer);}/*** ByteBuffer接受的数据 \n 切割为完整的行并打印** @param buffer ByteBuffer*/private static void doLineSplit(ByteBuffer buffer) {buffer.flip();for (int i = 0; i < buffer.limit(); i++) {if (buffer.get(i) == '\n') {int length = i + 1 - buffer.position();ByteBuffer target = ByteBuffer.allocate(length);for (int j = 0; j < length; j++) {// 这里存在问题,target 在创建的时候大小固定了// 如果 buffer 中数据超过了 target 的大小,就会抛出 BufferOverflowExceptiontarget.put(buffer.get());}// 截取工作完成target.flip();System.out.println("StandardCharsets.UTF_8.decode(target).toString() = " + StandardCharsets.UTF_8.decode(target).toString());}}buffer.compact();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/71088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大语言模型】最新ChatGPT、DeepSeek等大语言模型助力高效办公、论文与项目撰写、数据分析、机器学习与深度学习建模等科研应用

ChatGPT、DeepSeek等大语言模型助力科研应用 随着人工智能技术的快速发展&#xff0c;大语言模型如ChatGPT和DeepSeek在科研领域的应用正在为科研人员提供强大的支持。这些模型通过深度学习和大规模语料库训练&#xff0c;能够帮助科研人员高效地筛选文献、生成论文内容、进行数…

人工智能之数学基础:线性子空间

本文重点 在前面的课程中,我们学习了线性空间,本文我们我们在此基础上学习线性子空间。在应用中,线性子空间的概念被广泛应用于信号处理、机器学习、图像处理等领域。 子空间的性质 子空间是线性空间的一部分,它需要满足下面的性质: 设V是数域F上的线性空间,W是V的一个…

【清晰教程】本地部署DeepSeek-r1模型

【清晰教程】通过Docker为本地DeepSeek-r1部署WebUI界面-CSDN博客 目录 Ollama 安装Ollama DeepSeek-r1模型 安装DeepSeek-r1模型 Ollama Ollama 是一个开源工具&#xff0c;专注于简化大型语言模型&#xff08;LLMs&#xff09;的本地部署和管理。它允许用户在本地计算机…

deepseek部署在本地详细教程

最近&#xff0c;DeepSeek爆火&#xff0c;先进的算法、卓越的能力&#xff0c;表现出众&#xff0c;其凭一己之力推动国内Ai大模型跨越式发展。作为一款现象级的Ai产品&#xff0c;用户量暴增&#xff0c;最近服务器又被攻击&#xff0c;使用DeepSeek&#xff0c;经常出现服务…

DeepSeek v3 技术报告阅读笔记

注 本文参考 DeepSeek-v3 / v2 / v1 Technical Report 及相关参考模型论文本文不包括基础的知识点讲解&#xff0c;为笔记/大纲性质而非教程&#xff0c;建议阅读技术报告原文交流可发送至邮箱 henryhua0721foxmail.com 架构核心 核心&#xff1a; MLA 高效推理DeepSeekMOE 更…

浏览器安全学习

浏览器特性 会将一些特殊符号当做字母进行解析&#xff0c;此时一个符号可能会被解析成两个到三个字母&#xff0c;这样子如果有漏洞对输入做了限制&#xff0c;黑客就可以利用这个特性来绕过某些漏洞中长度限制。某些特殊字符或者其他国家的文字和某些字母的形状一模一样&…

2025年AI免费大战:从DeepSeek到GPT-5的商业逻辑与行业变革

引言&#xff1a;人工智能行业的2025年重大转折 2025年伊始&#xff0c;人工智能行业的竞争格局发生了深刻变化&#xff0c;尤其是以DeepSeek为代表的新兴力量&#xff0c;通过低成本开源策略迅速崛起&#xff0c;迫使OpenAI、百度文心一言等人工智能巨头纷纷调整策略&#xf…

Word写论文常用操作的参考文章

1.插入多个引用文献&#xff1a;word中交叉引用多篇参考文献格式[1-2]操作以及显示错误问题 更改左域名&#xff0c;输入 \#"[0" 更改右域名&#xff0c;输入 \#"0]" 2.插入题注&#xff1a;word 中添加图片题注、目录、内部链接 3.插入公式编号&#x…

国产化替代大势所趋,ARM工控机的未来之路

在全球技术竞争加剧和国家政策推动的背景下&#xff0c;中国正在经历一场前所未有的国产化替代浪潮。在这个过程中&#xff0c;基于ARM架构的工业控制计算机&#xff08;简称ARM工控机&#xff09;迎来了前所未有的发展机遇&#xff0c;同时也面临着诸多挑战。 机遇 技术创新驱…

uniapp商城之登录模块

文章目录 一、小程序快捷登录1.定义接口2.获取登录凭证 code3.获取手机号并登录 二、模拟快捷登录1.封装模拟登录API2.调用模拟登录 三、保存登录信息1.类型声明2.状态管理3.成功提示并跳转页面 一、小程序快捷登录 1.定义接口 2.获取登录凭证 code 3.获取手机号并登录 注意&a…

C++-----------酒店客房管理系统

酒店客房管理系统 要求&#xff1a; 1.客房信息管理:包括客房的编号、类型、价格、状态等信息的录入和修改; 2.顾客信息管理:包括顾客的基本信息、预订信息等的管理; 3.客房预订:客户可以根据需要进行客房的预订&#xff0c;系统会自动判断客房的可用情况; 4.入住管理:客户入住…

电动汽车电池监测平台系统设计(论文+源码+图纸)

1总体设计 本次基于单片机的电池监测平台系统设计&#xff0c;其整个系统架构如图2.1所示&#xff0c;其采用STC89C52单片机作为控制器&#xff0c;结合ACS712电流传感器、TLC1543模数转换器、LCD液晶、DS18B20温度传感器构成整个系统&#xff0c;在功能上可以实现电压、电流、…

2025年02月11日Github流行趋势

项目名称&#xff1a;unsloth 项目地址url&#xff1a;https://github.com/unslothai/unsloth项目语言&#xff1a;Python历史star数&#xff1a;27175今日star数&#xff1a;1024项目维护者&#xff1a;danielhanchen, shimmyshimmer, Erland366, Datta0, xyangk项目简介&…

[qt5学习笔记]用vs2022(msvc2017)+copilot进行QtWidgetsApplication源码解析

一直没深入了解qt&#xff0c;又一段时间没写qt&#xff0c;重新捡起来。 开发环境 本地vs2022(msvc2017, v14.30)先升级到最新版本&#xff0c;方便使用copilot。 参考 VS2022QT5环境搭建 下载 qt5.14.2 用vs的qt插件设置qt5.14.2x86路径&#xff0c;x64版本未安装。 创建一…

家里装修想用投影仪,如何选择?装修中应该注意什么?

越来越多的业主在装修的时候抛弃了传统的电视&#xff0c;采用投影仪。 和这些业主聊天&#xff0c;选用投影仪有两个目的&#xff0c;第1是把电视机拿掉&#xff0c;这样能让家里的小朋友不看电视&#xff0c;保护小朋友的眼睛。 第2是选用投影仪&#xff0c;幕布都会装的比较…

javaEE初阶————多线程初阶(4)

8.1 单例模式 这又是什么新的神奇玩意呢&#xff0c;我们先不谈单例模式&#xff0c;先来谈谈设计模式&#xff0c;什么是设计模式呢&#xff0c;我们只需要用设计模式就好了&#xff0c;而大佬们考虑的就多了&#xff0c;这些设计模式就像棋谱&#xff0c;只要按照棋谱来下&am…

能源物联网数据采集网关 多协议对接解决方案

安科瑞刘鸿鹏 摘要 随着配电系统智能化需求的提升&#xff0c;现代配电物联网&#xff08;IoT&#xff09;系统对数据采集、传输、处理及远程管理能力提出了更高要求。智能网关作为连接现场设备与上层管理平台的核心枢纽&#xff0c;其性能直接影响系统的实时性、可靠性与扩展…

Node.js 中的 Event 模块详解

Node.js 中的 Event 模块是实现事件驱动编程的核心模块。它基于观察者模式&#xff0c;允许对象&#xff08;称为“事件发射器”&#xff09;发布事件&#xff0c;而其他对象&#xff08;称为“事件监听器”&#xff09;可以订阅并响应这些事件。这种模式非常适合处理异步操作和…

Unity开发抖音小游戏播放视频

Unity开发抖音小游戏播放视频 介绍抖音小程序ios端视频无法播放RenderTexture问题总结 介绍 最近在做抖音小游戏播放视频&#xff0c;这里我使用的是Unity原生的VideoPlayer组件来播放视频&#xff0c;这里总结了一下我相关的报错以及能够正常播放视频的代码。如果还不知道怎么…

网络安全抑制 缓解 根除 恢复 网络安全如何解决

一、网络安全 网络是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 二、如何防范网络安全问题 1、防范网络病毒。 2、配置防火墙。 3、采…