Java NIO ByteBuffer 使用方法

前言

最近在使用spring boot + websocket + xterm.js 给 k8s pod做了个在线的 web 终端,发现websocket的类核心方法,用的都是ByteBuffer传递数据,如下:

    @OnMessagepublic void onMessage(Session session, ByteBuffer byteBuffer) {//xxxxx}

以前只知道 NIO 里面大量用到了 ByteBuffer ,并没有仔细了解过,这次特意学习了一下,因为JDK自带的ByteBuffer 可以切换读写两种模式加上内置很多方法组合使用,有很多约定俗成的用法,稍不注意就有可能踩坑,这也是为什么Netty里面又基于 ByteBuffer 重新封装了ByteBuf类,就是因为 JDK 自带的太难用了

UML 图概览

解释:

Buffer 抽象类是所有 ByteBuffer 类的父类,其子类还有8种基本类型的IntBuffer,LongBuffer等,这不是我们这次的重点,我们这次主要关注 ByteBuffer 子类,如上图所示。

Buffer抽象类几个字段:

  • capacity:这个很好理解,它规定了整个 Buffer 的容量,具体可以容纳多少个元素。capacity 指针之前的元素均是 Buffer 可操作的空间。
  • position:用于指向 Buffer 中下一个可操作性的元素,初始值为 0。在 Buffer 的写模式下,position 指针用于指向下一个可写位置。在读模式下,position 指针指向下一个可读位置。
  • limit:表示 Buffer 可操作元素的上限。什么意思呢?比如在 Buffer 的写模式下,可写元素的上限就是 Buffer 的整体容量也就是 capacity ,capacity - 1 即为 Buffer 最后一个可写位置。在读模式下,Buffer 中可读元素的上限即为上一次 Buffer 在写模式下最后一个写入元素的位置。也就是上一次写模式中的 position。
  • mark:用于标记 Buffer 当前 position 的位置
    // Invariants: mark <= position <= limit <= capacityprivate int mark = -1; // 搭配 reset 使用private int position = 0; // 写模式下指向下一次写的位置,读模式下是当前要读数据的位置private int limit; private int capacity;// Used only by direct buffers// NOTE: hoisted here for speed in JNI GetDirectBufferAddresslong address;

MappedByteBuffer : 映射 JVM 堆外内存,也就是这部分内存由 linux 内核管理,其中可映射文件,也可也直接在操作堆上分配空间。最常用的是:DirectByteBuffer ,DirectByteBufferR 代表只读视图

HeapByteBuffer : 在 JVM 堆内分配内存,HeapByteBufferR 代表只读视图

常用方法
put

这个比较简单,就是向 ByteBuffer 里面放入数据,例子如下:

    public static  void putData(){//默认声明出来的是写模式ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'s','h'});System.out.println(buffer);//java.nio.HeapByteBuffer[pos=2 lim=16 cap=16]}
get

这个就要注意了,在没有切换成读模式下直接get是有问题的,除非指定 index 读

    public static  void getData(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'s','h'});System.out.println(buffer);System.out.println(buffer.position());System.out.println(buffer.get()); // 输出 0//在没有切换读模式下,get方法获取的是写 pos的值,也就是pos=2,所以读取不正确System.out.println(buffer.get(0)); // 输出 s}

此外,get方法也会使得 pos ++,所以几次get之后,在写数据就会出现空间空了几次:

    public static  void getData2(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'s','h'});System.out.println(buffer);System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++//在没有切换读模式下,get方法获取的是写 pos的值,也就是pos=2,所以读取不真确//java.nio.HeapByteBuffer[pos=2 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=3 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=4 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=5 lim=16 cap=16]//buffer.put(new byte[]{'e'});System.out.println(buffer);//最终内存结果//[115, 104, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

获取最后一位数据:

    public static  void getData3(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'s','h'});System.out.println(buffer);System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++System.out.println(buffer.get()); // 输出 0System.out.println(buffer); // get 方法会导致pos指针++//在没有切换读模式下,get方法获取的是写 pos的值,也就是pos=2,所以读取不真确//java.nio.HeapByteBuffer[pos=2 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=3 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=4 lim=16 cap=16]//0//java.nio.HeapByteBuffer[pos=5 lim=16 cap=16]//buffer.put(new byte[]{'e'});System.out.println(buffer);buffer.put(new byte[]{'\n'});//最终内存结果//[115, 104, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]// 使用getIndex获取最后一位数据, 并且不会导致pos++//        回车,ASCII码13
//        换行,ASCII码10
//        空格,ASCII码32// 输出10System.out.println(buffer.get(buffer.position()-1));}
flip

切换成读模式:limit和pos的值会自动适配变化,需要注意的是即使切换到读模式,你仍然可以写因为这不是强制的,但如果你切换成读模式后立马写数据,会覆盖掉第一位数据

    public static  void flip(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'t', 'o','m'});System.out.println(buffer);// java.nio.HeapByteBuffer[pos=3 lim=16 cap=16]buffer.flip();System.out.println(buffer); // java.nio.HeapByteBuffer[pos=0 lim=3 cap=16]System.out.println((char)buffer.get());System.out.println((char)buffer.get());System.out.println(buffer);// java.nio.HeapByteBuffer[pos=2 lim=3 cap=16]buffer.put(new byte[]{'e'});System.out.println(buffer);//}

覆盖写例子:

    // 切换读模式public static  void flip2(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'t', 'o','m'});System.out.println(buffer);// java.nio.HeapByteBuffer[pos=3 lim=16 cap=16]buffer.flip();System.out.println(buffer); // java.nio.HeapByteBuffer[pos=0 lim=3 cap=16]buffer.put(new byte[]{'e'});System.out.println(buffer); // java.nio.HeapByteBuffer[pos=1 lim=3 cap=16]System.out.println((char)buffer.get(0)); //写的数据覆盖了第一个t}
rewind

读模式下重置数据,从头开始读:

    public static void  rewind(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'t', 'o'});buffer.flip();System.out.println((char)buffer.get()); // tSystem.out.println((char)buffer.get()); // o// 从头开始读buffer.rewind();System.out.println((char)buffer.get()); // tSystem.out.println((char)buffer.get()); // o}
mark & reset

mark标记当前位置,继续读写后,然后reset可以重置到mark的位置,实现原理很简单就是用mark字段备份了原来pos的值:

    public static void markResetRead(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'t', 'o', 'm', 'c','a','t'});System.out.println(buffer);buffer.flip();System.out.println((char) buffer.get()); // tSystem.out.println((char) buffer.get()); // o// 控记住当前的 positionbuffer.mark();System.out.println((char) buffer.get()); // mSystem.out.println((char) buffer.get()); // cbuffer.reset();System.out.println((char) buffer.get()); // mSystem.out.println((char) buffer.get()); // c}

写模式也可以:

    public static void markResetWrite(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.mark();buffer.put(new byte[]{'a'});buffer.put(new byte[]{'b'});// 控记住当前的 positionSystem.out.println((char) buffer.get(0)); // aSystem.out.println((char) buffer.get(1)); // bbuffer.reset();buffer.put(new byte[]{'c'});buffer.put(new byte[]{'d'});System.out.println((char) buffer.get(0)); // cSystem.out.println((char) buffer.get(1)); // d}
clear

重置写模式,注意这个并没有删除旧数据,只是把pos位置置0:

   // 从头开始写覆盖数据public static void clear(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put(new byte[]{'a', 'b', 'c', 'd'});System.out.println(buffer);//java.nio.HeapByteBuffer[pos=4 lim=16 cap=16]buffer.clear();System.out.println(buffer);//java.nio.HeapByteBuffer[pos=0 lim=16 cap=16]}
compact

compact方法,主要是用来解决clear方法切换写模式后,总是从头开始的问题,因为切换为读的时候,大部分情况下可能只读一部分数据,然后就要切写模式,直接掉clear方法会覆盖掉一部分未读的数据,所以这个时候需要使用compact方法,将没读的部分移动到前面,然后将pos重置到下一个可覆盖写的地方

    public static void compact(){ByteBuffer buffer = ByteBuffer.allocate(16);buffer.put("hadoop".getBytes(StandardCharsets.UTF_8));System.out.println(buffer);//java.nio.HeapByteBuffer[pos=6 lim=16 cap=16]// 切换到读模式buffer.flip();String v=StandardCharsets.UTF_8.decode(buffer).toString();System.out.println(v);//hadoopbuffer.rewind(); //重置读System.out.println((char) buffer.get()); // hSystem.out.println(buffer);//java.nio.HeapByteBuffer[pos=1 lim=6 cap=16]// 数据读了一部分,这个时候使用clear切换写模式,会覆盖掉没读部分,所以得使用 compat 将没读过的数据, 移到 buffer 的首部buffer.compact(); // 此时 buffer 的数据就会变成 adooppSystem.out.println(buffer);// java.nio.HeapByteBuffer[pos=5 lim=16 cap=16]buffer.rewind();String v1=StandardCharsets.UTF_8.decode(buffer).toString();System.out.println(v1);//adoopp}
hasRemaining

判断 pos 位置是否小于 limit,也就是是否达到buffer的上限

    public static  void remaining(){ByteBuffer buffer = ByteBuffer.allocate(2);buffer.put(new byte[]{'b'});System.out.println(buffer.hasRemaining()); //truebuffer.put(new byte[]{'c'}); System.out.println(buffer.hasRemaining()); //false // check position < limit;}
remaining

写模式下,判断剩余容量还有多少:

    public static void remaining(){ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'b'});buffer.put(new byte[]{'c'});System.out.println(buffer.remaining()); // 8}
其他方法:

isReadOnly: 判断是否是只读Buffer

isDirect: 是否从对外分配的内存空间

duplicate:

clone 原生 ByteBuffer。它们的 offset,mark,position,limit,capacity 变量的值全部是一样的,这里需要注意虽然值是一样的,但是它们各自之间是相互独立的。用于对同一字节数组做不同的逻辑时候需要

slice:

调用 slice() 方法创建出来的 ByteBuffer 视图内容是从原生 ByteBufer 的当前位置 position 开始一直到 limit 之间的数据。也就是说通过 slice() 方法创建出来的视图里边的数据是原生 ByteBuffer 中还未处理的数据部分,共享原生的数据,访问时需要带上 offset

总结

Java 中的 ByteBuffer 是 java.nio 包中的核心类之一,属于 New I/O (NIO) 框架。它提供了用于操作字节数据的丰富方法,ByteBuffer 在需要高效 I/O 操作的应用程序中非常有用,特别是在网络编程、文件 I/O、内存映射文件、以及其他需要直接操作字节数据的场景中。使用 ByteBuffer 可以带来更好的性能和灵活性。

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

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

相关文章

vitepress搭建的博客系统cdn引入github discussions评论系统

github仓库必须是公开的。 按照CDN的方式引入 打开discussions模块 安装giscus app 配置giscus 就是刚安装了giscus app的仓库 页面往下走&#xff0c;生成了代码&#xff1a; 配置vitepress 采用了CDN的方式引入 使用web component 随便找个地方试试组件 效果 有了…

LeetCode43.字符串相乘【大整数相乘】

LeetCode刷题记录 文章目录 &#x1f4dc;题目描述&#x1f4a1;解题思路 &#x1f4dc;题目描述 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 Big…

记录一次centos扩容

背景 在Vscode上连虚拟机写项目&#xff0c;突然提示磁盘空间不足(no space left on device)&#xff0c;一开始打算删些东西&#xff0c;这里参考博客&#xff0c;写得挺清楚的&#xff0c;但是操作后我发现实在没啥文件可以删除&#xff0c;所以干脆不删了&#xff0c;直接扩…

创建comfyui自定义节点

参考 https://github.com/liubai-liubai/ComfyUI-ImgSeg-LB/tree/main https://blog.styxhelix.life/?p33 安装 不需要安装任何其他依赖文件&#xff0c;只需要把0x_erthor_node文件夹复制到custom_nodes文件夹下&#xff0c;就能安装成功。 a1&#xff1a;展示了代码结构&…

ensp模拟器USG6000V1配置DCHP功能

接着上一篇配置&#xff0c;继续本篇的内容。开启DHCP功能非常简单&#xff0c;只需几个命令即可。实验拓扑图也非常简单&#xff0c;如下&#xff1a; 开启防火墙DHCP功能&#xff1a; [USG6000V1]dhcp enable 选择DHCP接口并设置接口IP地址&#xff0c;这里给g1/0/0配置2网…

数据库原理(关系型数据库基本理论)——(

一、关系的概念 1.关系的定义 &#xff08;1&#xff09;域 域是一组具有相同数据类型的值的集合&#xff0c;可以理解为int[]&#xff08;int类型的数组&#xff09;是一个域。 &#xff08;2&#xff09;笛卡儿积 简单来说&#xff0c;若干个域的笛卡儿积就是将这几个域的…

MySQL日志(三):数据安全

先来看一个结论&#xff1a;只要redo log和binlog保证持久化到磁盘&#xff0c; 就能确保MySQL异常重启后&#xff0c; 数据可以恢复。 binlog写入逻辑 binlog的写入逻辑比较简单&#xff1a; 事务执行过程中&#xff0c; 先把日志写到binlog cache&#xff0c; 事务提交的时候…

Linux:线程池

Linux&#xff1a;线程池 线程池概念封装线程基本结构构造函数相关接口线程类总代码 封装线程池基本结构构造与析构初始化启动与回收主线程放任务其他线程读取任务终止线程池测试线程池总代码 线程池概念 线程池是一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影…

STM32CubeMX配置-外部中断配置

一、简介 MCU为STM32G070&#xff0c;配置为上升沿触发外部中断&#xff0c;在上升沿外部中断回调函数中进行相关操作。 二、外部中断配置 查看规格书中管教描述&#xff0c;找到I/O对应的外部中断线&#xff0c;然后进行如下上升沿触发外部中断配置。 三、生成代码 调用上升沿…

JavaScript 规范霍夫曼编码

霍夫曼编码是一种无损数据压缩算法&#xff0c;其中数据中的每个字符都分配有可变长度的前缀代码。出现频率最低的字符获得最大代码&#xff0c;出现频率最高的字符获得最小代码。使用这种技术对数据进行编码非常简单且高效。但是&#xff0c;解码使用此技术生成的比特流效率低…

Parallels Desktop 19 激活码 - 苹果 Mac 最新版 PD 19激活密钥虚拟机下载 (支持Win11/macOS Sonoma)

Parallels Desktop 被称为 macOS 上强大的虚拟机软件。可以在 Mac 下同时模拟运行 Win、Linux、Android 等多种操作系统及软件而不必重启电脑&#xff0c;并能在不同系统间随意切换。 最新版 Parallels Desktop 19 (PD19) 完全支持 macOS Sonoma、Ventura 和 Windows 11 / Win…

【Ardiuno】实验使用OPT语音模块播放语音(图文)

当我们需要在程序中播放语音内容时&#xff0c;就需要使用到语音模块&#xff0c;今天我们就来实验一下使用OPT语音模块来方法语音。 const int voicePin 5; const int voiceBusyPin 18; const int testLEDPin 2;unsigned long pmillis 0;int busyVal 0; …

LeetCode | 125.验证回文串

这道题一开始的想法是把原字符串的非数字英文字符去掉&#xff0c;然后判断剩下的字符串是否为回文串即可&#xff0c;其中去掉非数字英文字符可以遍历一遍字符串依次处理&#xff0c;也可以用正则表达式&#xff0c;然后判断是否是回文串只需要两个指针&#xff0c;一头一尾&a…

OpenCV目标识别

一 图像轮廓 具有相同颜色或强度的连续点的曲线。 图像轮廓的作用 可以用于图像分析 物体的识别与检测 注意 为了检测的准确性&#xff0c;需要先对图像进行二值化或Canny操作。 画轮廓时会修改输入的图像。 轮廓查找的API findContours(img,mode,ApproximationMode,...)…

upload-labs第八关教程

upload-labs第八关教程 一、源代码分析代码审计 二、绕过分析点绕过上传eval.php使用burp suite进行抓包修改放包&#xff0c;查看是否上传成功使用中国蚁剑进行连接 一、源代码分析 代码审计 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(U…

推箱子-小游戏

学习目标&#xff1a; 巩固Java基础&#xff0c;数据类型、二维数组、条件语句等&#xff1b; 效果展示&#xff1a;

CSS【详解】样式选择器的优先级(含提升优先级的方法)

数值越大&#xff0c;优先级越高&#xff0c;尽量保持较低的优先级&#xff0c;以便使用更高优先级的选择器重置样式 0级——通配选择器、选择符和逻辑组合伪类。逻辑组合伪类有:not()、:is()和:where等&#xff0c;这些伪类本身并不影响CSS优先级&#xff0c;影响优先级的是括…

Python基础用法 之 变量

1.变量的定义 变量的作用&#xff1a;是⽤来保存数据的。定义的语法&#xff1a;变量名 数据值使用&#xff1a;直接使⽤变量名 即可使⽤变量中存储的数据。注意&#xff1a;变量必须先定义后使用。 (即 必须 先存⼊数据 才能 获取数据) 。 # 需求 1, 定义⼀个变量 保存你的名…

(超详细)基于动态顺序表实现简单的通讯录项目

前言&#xff1a; 我们在上一章节用c语言实现了线性表中的的动态顺序表&#xff0c;那么顺序表就只是顺序表吗&#xff1f;当然不是&#xff0c;使用顺序表结构可以实现很多项目&#xff0c;许多项目的数据结构都会用到顺序表&#xff0c;本章节我们就要使用顺序表实现一个简易…

【论文阅读】AttnDreamBooth | 面向文本对齐的个性化图片生成

文章目录 1 动机2 方法3 实验 1 动机 使用灵活的文本控制可以实现一些特定的概念的注入从而实现个性化的图片生成。 最经典的比如一些好玩的动漫人物的概念&#xff0c;SD大模型本身是不知道这些概念的&#xff0c;但是通过概念注入是可以实现的从而生成对应的动漫人物 两个…