Java NIO ByteBuffer 使用方法

前言

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

    @OnMessage
    public 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 <= capacity
    private 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 GetDirectBufferAddress
    long 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()); // 输出 0
        System.out.println(buffer); // get 方法会导致pos指针++
        System.out.println(buffer.get()); // 输出 0
        System.out.println(buffer); // get 方法会导致pos指针++
        System.out.println(buffer.get()); // 输出 0
        System.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()); // 输出 0
        System.out.println(buffer); // get 方法会导致pos指针++
        System.out.println(buffer.get()); // 输出 0
        System.out.println(buffer); // get 方法会导致pos指针++
        System.out.println(buffer.get()); // 输出 0
        System.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

        // 输出10
        System.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()); // t
        System.out.println((char)buffer.get()); // o

        // 从头开始读
        buffer.rewind();

        System.out.println((char)buffer.get()); // t
        System.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()); // t
        System.out.println((char) buffer.get()); // o
        // 控记住当前的 position
        buffer.mark();
        System.out.println((char) buffer.get()); // m
        System.out.println((char) buffer.get()); // c

        buffer.reset();
        System.out.println((char) buffer.get()); // m
        System.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'});
        // 控记住当前的 position

        System.out.println((char) buffer.get(0)); // a
        System.out.println((char) buffer.get(1)); // b

        buffer.reset();
        buffer.put(new byte[]{'c'});
        buffer.put(new byte[]{'d'});

        System.out.println((char) buffer.get(0)); // c
        System.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);//hadoop
        buffer.rewind(); //重置读
        System.out.println((char) buffer.get()); // h
        System.out.println(buffer);//java.nio.HeapByteBuffer[pos=1 lim=6 cap=16]
        // 数据读了一部分,这个时候使用clear切换写模式,会覆盖掉没读部分,所以得使用 compat 将没读过的数据, 移到 buffer 的首部
        buffer.compact(); // 此时 buffer 的数据就会变成 adoopp
        System.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()); //true
        buffer.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.mfbz.cn/a/714923.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习量化交易1】一个金融小白尝试量化交易的设想、畅享和遐想

关注我的朋友们可能知道&#xff0c;我经常在信号处理的领域出没&#xff0c;时不时会发一些信号处理、深度学习科普向的文章。 不过算法研究久了&#xff0c;总想做一些更有趣的事情。 比如用深度学习算法赚大钱。。毕竟有什么事情能比暴富更有意思呢。 一、神经网络与彩票…

Vue - 实现登录页面

1、技术框架 1、技术框架Vue-Cli Vue3.0 2、界面推荐大小&#xff1a; 1920 * 1080 3、UI组件&#xff1a;elementui 4、icon: element-plus/icons-vue 5、node版本&#xff1a;v20.14.0 2、效果图 3、源代码部分截图 4、其他 有需要的请联系作者。需要购买&#xff0c;不白…

Science:如何快速完成一篇研究性论文?

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 完成一篇研究性论文&#xff0c;是将长时间积累的研究成果凝聚在几页纸中&#xff0c;对资深科学家而言也是一大挑战。作者们需要在充分论述科学问题和详细展示结果之间找到平…

【漏洞复现】东胜物流软件 GetProParentModuTreeList SQL注入漏洞

0x01 产品简介 东胜物流软件是青岛东胜伟业软件有限公司-款集订单管理、仓库管理、运输管理等多种功能于一体的物流管理软件。该公司初创于2004年11月(前身为青岛景宏物流信息技术有限公司)&#xff0c;专注于航运物流相关环节的产品和服务。东胜物流信息管理系统货代版采用MS…

Linux:线程池

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

C++类和对象(1):构造函数和析构函数

一.类与对象的基本概念 1.结构体与类 C提供了一种比结构体类型更安全有效的数据类型——类。并且用class取代struct。 在C中将类的成员分为两类&#xff1a;私有成员(用private说明)和公有成员(用public说明)。私有成员(包括数据成员和成员函数)只能被类内的成员函数访问&am…

CSS从入门到精通——动画:CSS3动画执行次数和逆向播放

目录 任务描述 相关知识 动画执行次数 动画反向播放 编程要求 任务描述 本关任务&#xff1a;用 CSS3 实现loading效果。效果图如下&#xff1a; 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.动画执行次数&#xff0c;2.动画反向播放。 需要实现的效…

每天五分钟深度学习框架pytorch:多维tensor向量在某一维度的拼接和分割

本文重点 在深度学习中,我们常常需要完成多个向量拼接,同时也要完成向量的分割,在pytorch中已经有封装好的库,我们可以直接调用完成这部分任务。 Cat拼接 c=torch.cat([a,b],dim=0)表示将a和b按0维度进行拼接,需要注意再非dim维度,两个矩阵的维度必须是一致的,不然会拼…

docker拉取镜像一直在加载中,且会提示error pulling image configuration

1、增加国内镜像配置 #查看文件内容 sudo vim /etc/docker/daemon.json如果没有该文件&#xff0c;则需要在/etc/docker中创建一个daemon.json 文件 创建文件 vim daemon.json#文件中添加以下json {"registry-mirrors":["https://docker.mirrors.ustc.edu.cn/…

小工具开发

因不太喜欢重复性工作&#xff0c;为了提高日常工作效率&#xff0c;在业余时间开发一些小工具用于帮助自己“偷懒”。 小工具功能&#xff1a; 1、Hightec编译的hex文件&#xff0c;与多模式标定hex文件合成 2、Bootloader文件&#xff0c;Hightec编译的hex文件&#xff0c;与…

SpringMVC框架学习笔记(八):自定义拦截器和异常处理

1 自定义拦截器 1.1 什么是拦截器 1.1.1 说明 &#xff08;1&#xff09;Spring MVC 也可以使用拦截器对请求进行拦截处理&#xff0c;用户可以自定义拦截器来实现特定 的功能. &#xff08;2&#xff09;自定义的拦截器必须实现 HandlerInterceptor 接口 1.1.2 自定义拦截…

ssh的远程连接(Linux篇)

这里用到的虚拟机时centos7 记得提前先把网络连接好&#xff0c;这里选择的是桥接模式 1.启动ssh服务 # 在centos中启动sshd服务 sudo systemctl start sshd 2.在windows的cmd命令界面内输入以下内容 # ssh centos中的登录用户名centos中的IP地址 ssh yl192.168.1.108 然后…

Python 基础:类

目录 一、类的概念二、定义类三、创建对象并进行访问四、修改属性的值方法一&#xff1a;句点表示法直接访问并修改方法二&#xff1a;通过方法进行修改 五、继承继承父类属性和方法重写父类方法 六、将实例用作属性七、导入类导入单个类从一个模块中导入多个类导入整个模块导入…

效果超越ControlNet+IP-Adapter和FreeControl!Ctrl-X:可控文生图新框架(加州大学英伟达)

文章链接&#xff1a;https://arxiv.org/pdf/2406.07540 项目链接&#xff1a;https://genforce.github.io/ctrl-x/ 最近的可控生成方法&#xff0c;如FreeControl和Diffusion Self-guidance&#xff0c;为文本到图像&#xff08;T2I&#xff09;扩散模型带来了细粒度的空间…

机器学习(V)--无监督学习(一)聚类

根据训练样本中是否包含标签信息&#xff0c;机器学习可以分为监督学习和无监督学习。聚类算法是典型的无监督学习&#xff0c;目的是想将那些相似的样本尽可能聚在一起&#xff0c;不相似的样本尽可能分开。 相似度或距离 聚类的核心概念是相似度(similarity)或距离(distance…

模板方法模式和命令模式

文章目录 模板方法模式1.引出模板模式1.豆浆制作问题2.基本介绍3.原理类图 2.豆浆制作代码实现1.类图2.SoyaMilk.java 豆浆的抽象类3.PeanutSoyaMilk.java 花生豆浆4.RedBeanSoyaMilk.java 红豆豆浆5.Client.java6.结果 3.钩子方法1.基本介绍2.代码实现1.SoyaMilk.java 添加钩子…

数据中台-知识图谱平台

【数据分析小兵】专注数据中台产品领域,覆盖开发套件,包含数据集成、数据建模、数据开发、数据服务、数据可视化、数据治理相关产品以及相关行业的技术方案的分享。对数据中台产品想要体验、做二次开发、关注方案资料、做技术交流的朋友们&#xff0c;可以关注我。 1. 概述 随着…

弗洛伊德算法——C语言

弗洛伊德算法&#xff0c;是一种用于解决所有顶点对之间最短路径问题的经典算法&#xff0c;该算法通过动态规划的方法计算出从每个顶点到其他所有顶点的最短路径。 弗洛伊德算法的基本思想是逐步考虑每一个顶点作为中间点&#xff0c;更新所有顶点对之间的最短路径。它通过以…

开源模型应用落地-LangChain高阶-LCEL-表达式语言(七)

一、前言 尽管现在的大语言模型已经非常强大&#xff0c;可以解决许多问题&#xff0c;但在处理复杂情况时&#xff0c;仍然需要进行多个步骤或整合不同的流程才能达到最终的目标。然而&#xff0c;现在可以利用langchain来使得模型的应用变得更加直接和简单。 LCEL是什么&…

STM32CubeMX配置-RTC周期唤醒

一、简介 MCU为STM32G070&#xff0c;采用内部时钟32KHZ&#xff0c;配置为周期6s唤醒&#xff0c;调用回调函数&#xff0c;进行喂狗操作。 二、配置 初始时间、日期、周期唤醒时间配置。 开启周期唤醒中断 三、生成代码 调用回调函数&#xff0c;进行喂狗操作。 //RTC唤醒回…