Java NIO ByteBuffer 使用方法

news2024/11/28 20:55:44
前言

最近在使用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.coloradmin.cn/o/1815831.html

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

相关文章

MySQL-分组函数

041-分组函数 重点&#xff1a;所有的分组函数都是自动忽略NULL的 分组函数的执行原则&#xff1a;先分组&#xff0c;然后对每一组数据执行分组函数。如果没有分组语句group by的话&#xff0c;整张表的数据自成一组。 分组函数包括五个&#xff1a; max&#xff1a;最大值mi…

智造新篇章:MicroAlign融资助推高精度FA技术革新

随着智能化浪潮的汹涌澎湃&#xff0c;全球制造业正经历着前所未有的技术革新。MicroAlign&#xff0c;一家专注于高精度功能组装&#xff08;FA&#xff09;技术的创新企业&#xff0c;近日宣布完成了高达100万欧元的种子轮融资。这一轮融资不仅为MicroAlign注入了加速商业化的…

java基于Vue+Spring boot前后端分离架构开发的一套UWB技术高精度定位系统源码

java基于VueSpring boot前后端分离架构开发的一套UWB技术高精度定位系统源码 系统采用UWB高精度定位技术&#xff0c;可实现厘米级别定位。UWB作为一种高速率、低功耗、高容量的新兴无线局域定位技术&#xff0c;目前应用主要聚焦在室内外精确定位。在工业自动化、物流仓储、电…

【产品经理】发票系统简述

一、发票类型 增值税电子普通发票&#xff1a;简称电票 增值税普通发票和增值税专用发票&#xff0c;简称&#xff1a;纸票 蓝票&#xff1a;开票金额为正值的发票。红票&#xff1a;发票金额为负值的发票。 注&#xff1a;专票电子化系统国家目前在推&#xff0c;后续有更新…

digit 手写数据库笔记 (机械学习)

参考书籍 第三章内容 digit 手写数据库 # 最初的分类器 # digits 手写数字库import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn import tree # 性能评价相关的库 from sklearn import metrics# digits 数据加载 digits datase…

人工智能-机器学习算法是什么?

人工智能和机器学习是紧密相关的概念&#xff0c;可以说机器学习是人工智能的一个重要分支。机器学习是一门多学科交叉专业&#xff0c;涵盖概率论知识&#xff0c;统计学知识&#xff0c;近似理论知识和复杂算法知识&#xff0c;使用计算机作为工具并致力于真实实时的模拟人类…

一个小的画布Canvas页面,记录点的轨迹

Hello大家好&#xff0c;好久没有更新了&#xff0c;最近在忙一些其他的事&#xff0c;今天说一下画布canvas&#xff0c;下面是我的代码&#xff0c;实现了一个点从画布的&#xff08;0,0&#xff09;到&#xff08;canvas.width&#xff0c;canvas.height&#xff09;的一个实…

MYSQL数据库下载和安装(详细)

1.点击MySQL官网(后续照着图走) 2.软件下载完点击进入安装 设置要安装的路径然后点击OK,后面点击下一步 再点击下一步 MySQL推荐使用最新的数据库和相关客户端&#xff0c;mysql8换了加密插件&#xff0c;所以如果选第一种方式&#xff0c;很可能导致你的navicat等客户端连不上…

手把手教你,怎么用手机开发一个H5整蛊小游戏

前言&#xff1a; 相信在大家的认知里&#xff0c;做软件&#xff0c;做应用肯定都是通过电脑来进行开发的吧。但是你听说过用手机也可以开发软件吗&#xff1f;今天就教大家如何用手机轻松的开发出一款整蛊的H5小游戏。 首先我们需要借助一个工具CodeFlying&#xff0c;它能够…

为什么要分析电商用户数据?详解两大用户数据分析维度

零售电商行业的蓬勃发展带来了海量的客户数据&#xff0c;这些数据不仅记录了消费者的每一次点击、浏览、购买行为&#xff0c;还蕴含着巨大的商业价值。如何从这些数据中提炼出有价值的信息&#xff0c;成为电商企业提升竞争力、优化客户体验、实现可持续发展的关键。本文将深…

跟着AI学AI_08 NumPy 介绍

NumPy&#xff08;Numerical Python&#xff09;是一个用于科学计算的基础库&#xff0c;它为 Python 提供了支持大规模多维数组和矩阵 NumPy 介绍 NumPy&#xff08;Numerical Python&#xff09;是一个用于科学计算的基础库&#xff0c;它为 Python 提供了支持大规模多维数…

异常体系及自定义路径

异常( Exception) 定义&#xff1a; 异常代表程序出现的问题 图来自黑马程序员 分类&#xff1a; 运行时异常&#xff1a;RuntimeException以及其子类&#xff0c;编译阶段不会出现异常提醒&#xff0c;运行时出现的异常&#xff08;如数组越界异常&#xff09;编译时异常&am…

C++ 11 之 参数传递

c11参数传递.cpp #include <iostream> using namespace std;void swap1(int a, int b) {int temp a;a b;b temp;cout << "函数的a: " << a << endl;cout << "函数的b: " << b << endl; }void swap2(int *a,…

JUC并发编程第十一章——Synchronized与锁升级机制

1 入门知识介绍 synchronized锁&#xff0c;是不是默认实现了锁升级。代码中只需要直接使用synchronized&#xff0c;至于怎么从偏向锁升级为轻量锁再升级为重量级锁&#xff0c;这些底层jvm已经实现了。不需要程序员担心。 是的&#xff0c;Java 8中的synchronized关键字确实默…

为什么代理IP很难做到100%可用性?

在当今高度互联的网络环境中&#xff0c;代理IP已成为许多网络活动的重要支撑工具&#xff0c;从数据收集到业务推广&#xff0c;无所不包。然而&#xff0c;代理IP在很多场景中发挥着重要作用&#xff0c;却很难实现100%的可用性。 这种情况并非偶然&#xff0c;而是受到多重复…

如何给自己的项目实现在线测试的接口文档knife4j

配置实现Knife4j在线接口测试文档 为什么要是实现这个东西呢&#xff1f;肯定是对我们有用的&#xff0c;后端主要编写的就是接口&#xff0c;然后我们将接口编写好了之后肯定还是需要进行调试看是否能够正常使用且按照规范返回对应的数据。相信大家测试都是基本上使用的是一些…

JavaScript的数组(一维数组、二维数组、数组常用的方法调用)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Java MyBatis实战:QueryWrapper中的and和or拼接技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在Java Web开发中&#xff0c;MyBatis是一个非常流行的持久层框架。它通过XML或注解的方式将Java对象与数据库表进行映射&#xff0c;从而实现数据的增删改查操作。在使用MyBatis的过程中&#xff0c;经常…

景联文科技:打造亿级高质量教育题库,赋能教育大语言模型新未来

随着人工智能技术的持续进步&#xff0c;从广泛的通用大语言模型到针对各行业的垂直大语言模型&#xff0c;已成为人工智能大语言模型技术深化演进的必然趋势。 教育大语言模型是适用于教育场景、具有庞大规模参数、融合了广泛的通用知识和专业知识训练形成的人工智能模型。能为…

【牛客面试必刷TOP101】Day31.BM65 最长公共子序列(二)和BM66 最长公共子串

文章目录 前言一、BM65 最长公共子序列(二)题目描述题目解析二、BM66 最长公共子串题目描述题目解析总结 前言 一、BM65 最长公共子序列(二) 题目描述 描述&#xff1a; 给定两个字符串str1和str2&#xff0c;输出两个字符串的最长公共子序列。如果最长公共子序列为空&#xf…