Java NIO 关键概念之 Buffer

news/2024/5/5 18:44:54/文章来源:https://blog.csdn.net/Q54665642ljf/article/details/127665673

一、前言

Java NIO 的三大关键概念之一是 Buffer,在一些文章/源代码中,我们也经常会看到 Buffer 相关的信息。Buffer 到底是什么,Buffer 的基本使用方法是什么,这是本文主要要说的。

二、Buffer 的基本概念

Buffer自 JDK1.4 引入,是一个抽象类,在java.nio包下,定义了一些通用的方法,并不能直接创建对象,在使用时,需要通过其具体的基础数据类型子类来创建对象。Java 的基础数据类型中,除了boolean外,都有一个对应数据类型的子类,如 ByteBuffer、IntBuffer 等。

通俗点说,Buffer 是 Java 基础数据类型的数据容器,本质上其实就是一个相应基础数据类型的数组封装,并扩展了相关的属性、操作方法等以方便使用。(实现缓存数据、方便高效地操作数据,后面会有示例/源码说明)

Buffer 中,有几个重要的属性/概念,简单说明如下:

简单来说,capacity 是 Buffer 对应的数组的容量值,limit 是读/写的限制值,position 是数组中相关元素位置的索引值,这三个值都不会为负数,且这几个值的大小关系如下:

mark <= position <= limit <= capacity

复制代码

Buffer 作为一个数据容器,操作一个 Buffer 的一般过程如下:

java.nio.Buffer类作为一个抽象基类,提供了一些基本方法,如capacity()limit()等,可以返回其私有属性值,也提供了flip()(读/写模式切换)、clear()(清空数据)等设置属性值的操作方法,且这些方法都是用final关键字修饰的,不可被重写

而要创建一个 Buffer 对象,则只能通过对应基础数据类型的子类中的allocate()方法来实现,同样,写数据、读数据,也需要调用相应子类中的相关put()get()方法来实现。

三、Buffer 的基本使用 &源码分析

基本使用

首先,通过一个例子演示下 Buffer 的基本使用:

import java.nio.ByteBuffer;/** * Java Buffer demo *  * 输出的byteBuffer对象中,可以看到读/写等相关方法操作后,position、limit、capacity值的的变化。 * * @author : laonong */public class ByteBufferMain {    public static void main(String[] args) {        //创建总容量为10的Buffer对象        ByteBuffer byteBuffer = ByteBuffer.allocate(10);        System.out.println("创建Buffer后:" + byteBuffer);        //写入4个节点元素数据        byteBuffer.put((byte) 'a');        byteBuffer.put((byte) 'b');        byteBuffer.put((byte) 'c');        byteBuffer.put((byte) 'd');        System.out.println("写入数据后:" + byteBuffer);        //读写转换        byteBuffer.flip();        System.out.println("flip()方法读写转换后:" + byteBuffer);        //读取Buffer中的所有元素数据        System.out.print("读取Buffer中的数据:");        while (byteBuffer.hasRemaining()) {            System.out.print((char)byteBuffer.get());            if (byteBuffer.hasRemaining()) {                System.out.print(",");            }        }        System.out.println();        System.out.println("读取数据后:" + byteBuffer);        //重置Buffer        byteBuffer.clear();        System.out.println("clear()方法重置后:" + byteBuffer);    }}

复制代码

执行代码输出:

创建Buffer后:java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]写入数据后:java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]flip()方法读写转换后:java.nio.HeapByteBuffer[pos=0 lim=4 cap=10]读取Buffer中的数据:a,b,c,d读取数据后:java.nio.HeapByteBuffer[pos=4 lim=4 cap=10]clear()方法重置后:java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

复制代码

从输出结果可以看出,随着对 Buffer 进行各种读/写操作,Buffer 中的三个关键属性中,position、limit 的值在不断变化,capacity 的值固定不变,如下图所示:

源码分析

接下来,我们通过源码,看一下 Buffer 到底是如何实现示例中的相关功能的。

示例代码中,几个关键类的关系如下图所示:

1、java.nio.Buffer部分源码:

package java.nio;import java.util.Spliterator;/** * A container for data of a specific primitive type. */public abstract class Buffer {      //标记当前的position    private int mark = -1;    //读/写位置游标    private int position = 0;    //buffer读/写的上限边界值    private int limit;    //buffer的最大容量值    private int capacity;      //构造函数    Buffer(int mark, int pos, int lim, int cap) {       // package-private        if (cap < 0)            throw new IllegalArgumentException("Negative capacity: " + cap);        this.capacity = cap;        limit(lim);        position(pos);        if (mark >= 0) {            if (mark > pos)                throw new IllegalArgumentException("mark > position: ("                                                   + mark + " > " + pos + ")");            this.mark = mark;        }    }     /**    * 清除数据     *(可以看出,并不是真正意义上的删除buffer中的元素,而是重置属性的值[数据的索引值])    */    public final Buffer clear() {        position = 0;        limit = capacity;        mark = -1;        return this;    }    /**     * 翻转buffer       * (通过重置属性值的方式,实现切换buffer读/写模式的目的。【注意:只能一次从写模式翻转到读模式,不能反复翻转】)     */    public final Buffer flip() {        limit = position;        position = 0;        mark = -1;        return this;    }    /**     * 倒带buffer     *  (用于当buffer已经读过一遍了,但还需要从头读一次,则需要调用一次该方法)     */    public final Buffer rewind() {        position = 0;        mark = -1;        return this;    }      /**     * 判断position、limit之间是否还有元素 ()     */    public final boolean hasRemaining() {        return position < limit;    }      /**     * position值自增 (用于子类中写数据时调用)     */   final int nextPutIndex() {                          // package-private        if (position >= limit)            throw new BufferOverflowException();        return position++;    }     /**     * position值自增 (用于子类中读数据时调用)     */   final int nextGetIndex() {                          // package-private        if (position >= limit)            throw new BufferUnderflowException();        return position++;    }}

复制代码

2、java.nio.ByteBuffer 部分源码:(其它 IntBuffer、CharBuffer 等 Buffer 子类源码相似)

package java.nio;/** * A byte buffer. */public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {    /**     * 内部属性     *(这几个属性主要是在ByteBuffer的子类中使用,如HeapByteBuffer)     */    final byte[] hb;                  // Non-null only for heap buffers    final int offset;    boolean isReadOnly;                 // Valid only for heap buffers      /**     * 分配(创建)一个新的指定容量的 ByteBuffer     */    public static ByteBuffer allocate(int capacity) {        if (capacity < 0)            throw new IllegalArgumentException();        return new HeapByteBuffer(capacity, capacity);    }      /**     * 读数据      * (读取的是数组当前索引值为position的数据,读取后,position的值加1)     *     */    public abstract byte get();    /**     * 写数据      * (写数据的位置当前索引值为position,写数据后,position的值加1)     */    public abstract ByteBuffer put(byte b);      /**     * 压缩buffer     */    public abstract ByteBuffer compact();}

复制代码

java.nio.ByteBuffer 类中的get()put(byte b)compact()这几个方法都只有抽象定义,具体实现在子类中,接下来看下HeapByteBuffer中的具体实现代码。

3、java.nio.ByteBuffer 部分源码:

package java.nio;/** * A read/write HeapByteBuffer. */class HeapByteBuffer extends ByteBuffer {     /**  * 添加偏移的索引值  */  protected int ix(int i) {    return i + offset;  }   /**  * 写数据   */  public ByteBuffer put(byte x) {        hb[ix(nextPutIndex())] = x;        return this;  }   /**  * 读数据   */  public byte get() {    return hb[ix(nextGetIndex())];  }    /**  * 压缩buffer  * (复制数组中未读的元素,然后通过设置position、limit的值,将数据移动到buffer的头部,  *  这样的话,buffer继续写数据就不会覆盖未读的数据)  */  public IntBuffer compact() {        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());        position(remaining());        limit(capacity());        discardMark();        return this;    }}

复制代码

以上,笔者只贴了部分关键代码,代码中已添加简单说明。更多详细代码可以去看源码,源码中有非常详细的相关注释。

源码补充说明:

1、对 Buffer 的清除、压缩、翻转等操作,在某种意义上来说,其实就是对数据索引值 position、limit 的操作

2、Buffer 中的清除数据(clear()方法),并不会真正删除 Buffer 中数组中的元素,而是通过设置 position、limit 属性值的方式,修改 Buffer 的读/写位置与边界值。

3、如果 Buffer 中的数据没有全部读完,就调用clear()方法的话,则可能会造成未读数据被覆盖掉,此时,如果想只清除已读数据,保留未读数据的话,可调用compact()方法。

4、Buffer 通过优秀的设计实现了可以方便灵活、简单高效地缓存/操作数据。

Buffer 的常用方法简单说明如下

当然,buffer 的子类中还有许多其它方法,用于更灵活地操作 buffer,如put(int index, byte b);方法可指定索引值写数据,get(int index);方法可指定索引值读取数据等等,此处笔者就不一一列举了。

Buffer 的大小比较

另外,需要说明的是,Buffer 的具体基础数据类型子类都实现了Comparable接口,并重写了equals()compareTo()方法, 也就是说,Buffer 是可以比较大小的,只是需满足特定条件:

ByteBuffer 关键源码如下:

  public boolean equals(Object ob) {       if (this == ob)         return true;       if (!(ob instanceof ByteBuffer))         return false;       ByteBuffer that = (ByteBuffer)ob;       if (this.remaining() != that.remaining())         return false;       int p = this.position();       for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)         if (!equals(this.get(i), that.get(j)))           return false;       return true;  }  private static boolean equals(byte x, byte y) {       return x == y;  }  public int compareTo(ByteBuffer that) {      int n = this.position() + Math.min(this.remaining(), that.remaining());      for (int i = this.position(), j = that.position(); i < n; i++, j++) {        int cmp = compare(this.get(i), that.get(j));        if (cmp != 0)          return cmp;      }      return this.remaining() - that.remaining();  }  private static int compare(byte x, byte y) {      return Byte.compare(x, y);  }

复制代码

从源码可知:

1、判断两个 buffer 相等需满足以下条件:

a、有相同的元素类型;(同为 byte、int 等)

b、buffer 中剩余元素的个数相等;

c、buffer 中剩余元素的值都一一对应相等。

2、判断两个 buffer 大小根据以下条件判断:

a、有相同的元素类型;(同为 byte、int 等)

b、剩余第一个不相等元素的大小;(两个 buffer 中,从当前 position 开始依次比较;当出现第一个不相等的元素时,以该元素的大小比较结果,作为 buffer 大小的比较结果)

c、剩余元素前面比较都相等,更长的那个大。(若依次比较 buffer 中的剩余元素后,其中一个 buffer 的剩余元素已经全部比较完,另一个 buffer 还存在元素没有参与比较,则还存在元素的 buffer 大。)

从以上条件可以看出,判断 buffer 是否相等、buffer 的大小,只会校验 buffer 中的“剩余”元素,并不会校验全部元素,这点需要注意。(剩余元素指从 position 到 limit 之间的元素,也就是 limit - position的差值)

四、小结

1、Buffer 缓冲区本质上是一个基础数据类型的数组,但又不仅仅是一个数组,它提供了高效地对数据的结构化访问,以及跟踪数据元素的读/写位置。

2、对 Buffer 的读/写切换、清除、压缩等操作,在某种意义上来说,其实就是对数据元素索引值 position、limit 的操作。

3、Buffer 并没有提供直接删除数据元素的方法,而是只能覆盖。所以,当调用 clear() / compact() 方法后,只是重新设置了 position、limit、mark 的值,原来的数据还是在 Buffer 中的,在覆盖之前依然是可以读取到的。

4、Buffer 写满数据后,依然是可以继续写入的,只是需要设置写入的位置,并且写入新数据后原数据会被覆盖;同样,Buffer 中的数据是可以重复读取的,只要调用 rewind() 方法即可。(或者可指定索引位置读/写数据)

5、Buffer 可以比较大小,且比较时只会校验比较剩余元素。

6、Buffer 分配的最大容量创建时就固定了,不支持动态扩/缩容,Buffer 的读/写模式切换也较为不便等等(flip() 方法调用麻烦且易出错),这些也可以说是 Buffer 的不足之处。

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

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

相关文章

快速教你如何搭建数据驱动自动化测试框架?

一、前言 说到数据驱动自动化测试&#xff0c;你会不会有这样的疑问&#xff1a;数据怎么管理&#xff1f;数据怎么才能驱动测试用例执行&#xff1f;到底怎么样才算数据驱动&#xff1f;那么本篇文章就教你如何进行数据驱动测试&#xff0c;相信你一定能对数据驱动自动化测试…

拓端tecdat|R语言向量自回归模型(VAR)及其实现

全文链接&#xff1a;http://tecdat.cn/?p6916 原文出处&#xff1a;拓端数据部落公众号 澳大利亚在2008 - 2009年全球金融危机期间发生了这种情况。澳大利亚政府发布了一揽子刺激计划&#xff0c;其中包括2008年12月的现金支付&#xff0c;恰逢圣诞节。因此&#xff0c;零售…

巧妙使用多个旧路由器无线中继提升网络速度

巧用多个路由器进行无线桥接或无线中继&#xff0c;提升网络速度 一、设备选择 1、百兆旧路由器&#xff0c;3-4个&#xff0c;用于无线中继WIFI信号&#xff0c;输出给多WAN路由器&#xff08;DI-8200&#xff09; 历史遗留百兆旧路由器3个&#xff0c;型号分别为腾达FH456…

3、用手机模拟器上的Autojs连接电脑vscode

文章目录1、下载模拟器2、在模拟器上安装Autojs3、连接vscode1、下载模拟器 这里推荐雷电模拟器 https://www.ldmnq.com/?n6005 2、在模拟器上安装Autojs 3、连接vscode 打开autojs打开侧边栏&#xff0c;然后选择连接电脑&#xff0c;打开服务器模式然后复制这个IP地址 4.…

浅谈 Mybatis 动态数据源切换是如何实现的

前言 小憩是辣么的让人神往&#xff0c;就像备战高考靠窗位置的那个你&#xff0c;肆无忌道的放空自己&#xff0c;望着深蓝色宁静的天空&#xff0c;思考着未来该何去何从&#xff0c;近处一颗高大魁梧的银杏树在炎炎夏日中尽情的摇曳着自己嫩绿的枝丫&#xff0c;迸发出无尽的…

计算机毕业设计ssm+vue基本微信小程序的高速公路服务区充电桩在线预订系统 uniapp 小程序

项目介绍 随着网络技术的发展,当前人们的生活模式发生了巨大的变化,特别是以电子商务为代表的产业影响了人们的生活。当前,电子商务成为振兴国家经济的重要手段,电子商务为人们的生活提供了极大的便利,帮助企业降低销售成本,提高销售效率。高速公路服务区作为传统的实体行业,经…

BGP BFD测试案例

一、BFD原理 1.1 BFD技术简介 一种全网统一、检测迅速、监控网络中链路或者IP路由的双向转发连通状况&#xff0c;并未上层应用提供服务的技术。 1.2 BFD会话建立方式和监测机制 ●BFD的标识符&#xff1a; &#xff08;1&#xff09;BFD建立会话存在标识符的概念&#xff…

中小企业数字化思考:数字化转型应该走自己的路

随着数字化的发展&#xff0c;以及数字中国概念的形成&#xff0c;和以前国央企宣布数字化转型时的不同&#xff0c;现在越来越多的企业开始寻求数字化转型&#xff0c;促使自身业务能够更好的发展。现在看过去&#xff0c;各行各业都有大量企业进行了数字化转型规划&#xff0…

【Mac】VSCode 更新1.73版本后JSTS代码跳转异常

前言 今天有小伙伴MacOS更新了VS Code版本后&#xff0c;说工程内的代码跳转全部异常了&#xff0c;没法正确跳转。搞了两三个小时没搞出来&#xff0c;找到了我&#xff0c;让我帮忙瞧瞧。排查下来发现这问题有点意思&#xff0c;故此记录一下。 问题 排查姿势 1. 提示没有定…

Skywalking9.2.0监控浏览器

Skywalking9.2.0监控浏览器 安装skywalking-client-js npm install skywalking-client-js --save在main.js添加信息 import ClientMonitor from skywalking-client-jsrouter.afterEach(() > {ClientMonitor.setPerformance({service: 服务名,serviceVersion: 版本号,pagePat…

基于模糊小波神经网络的空中目标威胁评估(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 在现代战争中, 随着信息化和智能化的飞速发展, 以及作战环境的日益复杂, 实时而准确地评估目标威胁, 不仅为空战决策提供科学的…

程序人生:技术水平低,就这还敢写自动化项目实战经验丰富?

今年部门要招两个自动化测试&#xff0c;这几个月我面试了几十位候选人。发现一个很奇怪的现象&#xff0c;面试中一问到元素定位、框架api、脚本编写之类的&#xff0c;很多候选人都对答如流。但是一问到实际项目&#xff0c;比如 “如何从0开始搭建自动化体系”、“如果让你来…

资深大牛纯手写RabbitMQ 核心笔记,还有谁?

RabbitMQ简介 RabbitMQ是消息代理(Message Broker)&#xff0c;它支持多种异步消息处理方式&#xff0c;最常见的有&#xff1a; Work Queue&#xff1a;将消息缓存到一个队列&#xff0c;默认情况下&#xff0c;多个worker按照Round Robin的方式处理队列中的消息。每个消息只…

CART回归树算法

【题目1】 表1为拖欠贷款人员训练样本数据集,使用CART算法基于该表数据构造决策树模型,并使用表2中测试样本集确定剪枝后的最优子树。 表1 拖欠贷款人员训练样本数据集编号 房产状况 婚姻情况 年收(千元) 拖欠贷款1 是 单身 125 否2 否 已婚 100 否3 否 单身 70 否4 是 已婚…

一本通1064;奥运奖牌计数

#include <iostream> using namespace std; int main() {int n, Jin, Yin, Tong;int JinSum 0, YinSum 0, TongSum 0, sum;cin >> n;for (int i 1; i < n; i) // 循环n次{cin >> Jin >> Yin >> Tong; // 输入一天获得的金银铜牌数JinSum …

IR信息检索前沿梳理

1. 检索预训练 1.1 PROP: Pre-training with Representative Words Prediction for Ad-hoc Retrieval three types of pre-training tasks have been proposed including: Inverse Cloze Task (ICT): The query is a sentence randomly drawn from the passage and the docu…

全志F1C芯片参数对比,供查阅

F1C600特性介绍 组合32M DDR1&#xff0c;QFN编解码模式&#xff0c;生产音频核心板&#xff08;CPUNORWIFI&#xff09;在WIFI站下播放的功率约0.5W组合I2S、SPDIF、CODEC等多功能接口支持全格式音频解码芯片 F1C600参数介绍 中央处理器 ARM926EJ-S 内存 SIP DDR1 SD2.0…

月入18000,0基础转行软件测试,实现薪资翻倍我只用了135天

在没做测试之前&#xff0c;我一直是个没自信的人&#xff0c;因为工作不稳定&#xff0c;收入也不高。 大学毕业做了2年酒店管理&#xff0c;月入4000提成&#xff0c;还经常上夜班&#xff0c;熬人又伤身体&#xff0c;于是不想再做服务行业&#xff0c;就转行做了电销。这之…

本地数据库IndexedDB - 学员管理系统之列表管理(二)

IndexedDB是浏览器提供的本地数据库&#xff0c;它可以被网页脚本创建和操作。IndexedDB允许存储大量数据&#xff0c;提供查找接口&#xff0c;还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言&#xff0c;IndexedDB不属于关系型数据库&#xff08;不支…