Java-NIO篇章(2)——Buffer缓冲区详解

news2024/11/5 22:08:25

Buffer类简介

Buffer类是一个抽象类,对应于Java的主要数据类型,在NIO中有8种缓冲区类,分别如下: ByteBuffer、 CharBuffer、 DoubleBuffer、 FloatBuffer、 IntBuffer、 LongBuffer、 ShortBuffer、MappedByteBuffer。 本文以它的子类ByteBuffer类为例子讲解。ByteBuffer子类就拥有一个byte[]类型的数组成员final byte[] hb,作为自己的读写缓冲区,数组的元素类型与Buffer子类的操作类型相互对应。Buffer类及其子类在NIO中的地位非常重要,接下来将介绍Buffer类的属性及其重要的方法。

Buffer 重要属性

Buffer类额外提供了一些重要的属性,其中有以下三个重要的成员属性:

  • capacity(容量),缓存数组的大小,一旦初始化就不能改变了,例如ByteBuffer创建实例时capacity为10那么只能写入10个Byte类型数据,同理如果是DoubleBuffer实例capacity为10那么只能写入10个Double类型数据。
  • position(读写位置),读写指针表示当前读取或者写入的数组下标位置,初始位置为0,当切换读写模式时其值会进行相应的调整,最大可读写位置为 limit-1。
    +limit(读写的限制),表示可以写入或者读取的最大上限,其属性值的具体含义,也与缓冲区的读写模式有关, 在写入模式下, limit属性值的含义为可以写入的数据最大上限。在刚进入到写入模式时, limit的值会被设置成缓冲区的capacity容量值,表示可以一直将缓冲区的容量写满。在读取模式下, limit的值含义为最多能从缓冲区中读取到多少数据。
  • mark (读写位置的临时备份),记住某个位置mark=position,再调用 reset()可以让 position 恢复到 mark 标记的位置,即 position=mark。通过mark和reset()可以对缓冲区中的数据循环重复读取某片段。

通过flip()方法可以切换读写模式,也就是主要重新设置position、 limit两个属性,如下面的例子,开始实例化一个缓冲区,初始模式为写模式,capacity为10,position默认为0,limit=capacity=10;写入5个数据后的状态如下面中间数组所示,如果此时调用flip()方法则切换到读模式,读模式下只能从缓冲区读取数据不能写,flip()方法将limit=position=5,而position重置为0,这个时候就能读取前面5个格子中的数据了。
在这里插入图片描述
以上的内容很重要,需要完全掌握,后面会给出对应的代码来实现上面的例子。mark这个属性后面结合代码再讲。这个图下面代码会反复提起,需要回来看下!

Buffer重要方法

allocate()创建缓冲区

上图第一个初始化的数组状态的代码如下,通过allocate申请容量为10的类型为Byte的缓冲区:

public static void main(String[] args) {
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    //        IntBuffer intBuffer = IntBuffer.allocate(10);
    System.out.println("positon: "+byteBuffer.position()); // positon: 0
    System.out.println("limit: "+byteBuffer.limit());      // limit: 10
    System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10
}

put()写入到缓冲区

紧接着上面allocate示例的代码,往byteBuffer中写入5个byte类型的数据,缓存区状态图对应上图的中间状态,代码如下:

// 往byteBuffer写入五个Byte类型的数据
// 也可以使用数组一次性写入,下面for循环等价于 byteBuffer.put(new byte[]{0,1,2,3,4});
for (int i = 0; i < 5; i++) {
	byteBuffer.put((byte) i); // 每次调用put,position都会自增1
}
System.out.println("positon: "+byteBuffer.position()); // positon: 5
System.out.println("limit: "+byteBuffer.limit());      // limit: 10
System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10

写入了5个元素之后,缓冲区的position属性值变成了5,所以指向了第6个(从0开始的)可以进行写入的元素位置。而limit最大可写上限、 capacity最大容量两个属性的值,都没有发生变化。put()方法接受一个Byte类型的参数将其写入到缓冲区中,并且position会自增1,直到position等于limit-1,如果大于等于limit则抛出异常!

flip()翻转

向缓冲区写入数据之后,是不可以直接从缓冲区中读取数据的,因为此时的 position 指向的是未写入数据的位置,如果需要读取写入的数据,例如上图的中间状态,需要将position指向第一个写入的数据,limit指向最后一个写入的数据,然后移动position一直到limit就可以读取到写入的数据。而flip()方法所做的就是将limit指向position,将position指向0,最后将mark清除的操作,也就是将写模式切换为读模式。同样紧接上面put示例的代码,代码如下:

// flip()将写模式切换为读模式
byteBuffer.flip();
System.out.println("positon: "+byteBuffer.position()); // positon: 0
System.out.println("limit: "+byteBuffer.limit());      // limit: 5
System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10

在读取完成后,如何再一次将缓冲区切换成写入模式呢?答案是:可以调用下面将讲到的Buffer.clear() 清空或者Buffer.compact()压缩方法,它们可以将缓冲区转换为写模式。总体的Buffer模式转换 :

flip()的源码如下:

public final Buffer flip() {
    limit = position; //设置可读的长度上限 limit,设置为写入模式下的 position 值
    position = 0; //把读的起始位置 position 的值设为 0,表示从头开始读
    mark = UNSET_MARK; // 清除之前的 mark 标记
    return this;
}

get()从缓冲区读取

使用调用flip方法将缓冲区切换成读取模式之后,就可以开始从缓冲区中进行数据读取了。读取数据的方法很简单,可以调用get()方法每次从position的位置读取一个数据,并且position自增1。 在position值和limit的值相等时,表示所有数据读取完成, position指向了一个没有数据的元素位置,已经不能再读了。此时再读,会抛出BufferUnderflowException异常。读完后如果需要再次写入需要调用Buffer.clear()或Buffer.compact()方法,即清空或者压缩缓冲区,将缓冲区切换成写入模式,让其重新可写。 紧接上面flip示例代码,代码如下:

// byte[] bytes = new byte[4];
// buffer.get(bytes); 
// 也可以全部一次性读取到一个数组中,下面for循环等于上面
for (int i = 0; i < 5; i++) {
    byte b = byteBuffer.get();
    System.out.print(b+" ");
} // 输出:0 1 2 3 4

System.out.println("positon: "+byteBuffer.position()); // positon: 5
System.out.println("limit: "+byteBuffer.limit());      // limit: 5
System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10

rewind()倒带

已经读完的数据,如果需要再读一遍,可以调用rewind()方法。 rewind()也叫倒带,就像播放磁带一样倒回去,再重新播放。 rewind()的源码如下:

public final Buffer rewind() {
    position = 0;//重置为 0,所以可以重读缓冲区中的所有数据
    mark = -1; // mark 标记被清理,表示之前的临时位置不能再用了
    return this;
}

可以看到,rewind将position重新指向了第一个可读数据,然后将mark清除,limit不变。紧接着get()的示例代码,测试rewind()如下:

// rewind 重新从头读取
byteBuffer.rewind();
System.out.println("positon: "+byteBuffer.position()); //positon: 0
System.out.println("limit: "+byteBuffer.limit());      // limit: 5
System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10

mark()reset()

mark( )和reset( )两个方法是成套使用的: Buffer.mark()方法将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark的值恢复到position中。 比如说要实现第2和第3个数据重复读三次,再读取后面的数据,上面的代码经过rewind后又可以从第1个数据从头读了(第一个数据是0,最后一个数据是4),那么要实现的效果输出结果应该是:“12121234”,实现代码如下:

// 测试mark和reset方法
byteBuffer.get(); // 第一个数据0不需要,现在position=1
byteBuffer.mark(); // 记录此时的position,mark = position = 1
for (int i = 0; i < 3; i++) {
    byteBuffer.reset(); // 使得 position = mark 而mark为1
    System.out.print(byteBuffer.get()+" "+byteBuffer.get()+" "); // 输出:1 2 1 2 1 2 ,而此时position为3
}
System.out.print(byteBuffer.get()+" "+byteBuffer.get()+" "); // 输出:3 4 ,而此时position为5

clear()清空缓冲区

上面基本都是介绍读模式的方法,如果此时又需要切换到写模式应该如何办呢?在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法的作用: (1)会将position清零; (2) limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。 即一旦调用clear()就可以将position=0,limit=capacity,这和缓存区刚被初始化出来的时候一样。调用clear就是写模式了,此时不可用从缓存区中读取数据了。代码如下:

// 测试 clear 方法
byteBuffer.clear();
System.out.println("positon: "+byteBuffer.position()); //positon: 0
System.out.println("limit: "+byteBuffer.limit());      // limit: 10
System.out.println("capacity: "+byteBuffer.capacity());// capacity: 10

compact()清空已读数据

compact的作用就是压缩缓冲区,将缓冲区从读模式转为写模式,比如说此时缓冲区中有5个数据,目前前面两个读完了,此时position为2,还有三个数据没有读取完,那么此时如果调用compact(),会将前面两个读完的数据删除并将三个未读的数据向左移动两个位置,此时position指向的是3,即第一个还没有写入的格子,此时缓冲区就是写模式,测试代码紧接着mark( )和reset( ) 测试后面:

 byteBuffer.rewind(); //重头读
 // 先读取两个
 byteBuffer.get(); // 读取了0
 byteBuffer.get(); // 读取了1
 System.out.println("positon: "+byteBuffer.position()); //positon: 2
 byteBuffer.compact(); // 缓存区压缩已读数据,转为写模式,此时缓冲区还有2 3 4 三个数据
 System.out.println("positon: "+byteBuffer.position()); //positon: 3
 byteBuffer.put((byte) 99); //positon: 4
 byteBuffer.flip(); //切换读模式,不切换的话下面get将输出4,也就是positon为4的那个数据
 System.out.println(byteBuffer.get()); //输出:2

使用Buffer类的基本步骤

总体来说,使用Java NIO Buffer类的基本步骤如下:

  1. 使用创建子类实例对象的allocate( )方法,创建一个Buffer类的实例对象。
  2. 调用put( )方法,将数据写入到缓冲区中。
  3. 写入完成后,在开始读取数据前,调用Buffer.flip( )方法,将缓冲区转换为读模式。
  4. 调用get( )方法,可以从缓冲区中读取数据。
  5. 读取完成后,调用Buffer.clear( )方法或Buffer.compact()方法,将缓冲区转换为写入模式,可以继续写入。

经典神书推荐:《Java高并发核心编程系列》——尼恩

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

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

相关文章

Yolov8_使用自定义数据集训练模型1

前面几篇文章介绍了如何搭建Yolov8环境、使用默认的模型训练和推理图片及视频的效果、并使用GPU版本的torch加速推理、导出.engine格式的模型进一步利用GPU加速&#xff0c;本篇介绍如何自定义数据集&#xff0c;这样就可以训练出识别特定物体的模型。 《Yolov8_使用自定义数据…

Mysql:重点且常用的 SQL 标签整理

目录 1 <resultMap> 标签 2 <sql> 标签 3 <where> 标签 4 <if> 标签 5 <trim> 标签 6 <foreach> 标签 7 <set> 标签 1 <resultMap> 标签 比如以下代码&#xff1a; <resultMap type"SysCollege" id&qu…

Scrcpy:掌握你的Android设备

Scrcpy&#xff1a;掌握你的Android设备 本文将介绍Scrcpy工具&#xff0c;它是一种强大的安卓设备控制工具&#xff0c;可以实现屏幕镜像、操作控制等功能。我们将探讨Scrcpy的基本原理和工作方式&#xff0c;并介绍如何使用Scrcpy连接和控制安卓设备。此外&#xff0c;我们还…

旅游项目day04

1. JWT有效期 封装用户登录对象&#xff0c; 在指定时间过期 2. 有些接口需要登录&#xff1f;有些不需要登录&#xff1f; 后端如何知道a需要登录&#xff0c;b不需要登录&#xff1f; 注解。 3. 目的地 一个区域下面包含多个目的地 数据库表&#xff1a; 1. 区域表 2.…

VS2022联合Qt5开发学习9(QT5.12.3鼠标按下、释放、移动事件以及Qt上取标注点)

在研究医学图像可视化的时候&#xff0c;鼠标响应这里一直都有问题。研究了几天VTK的取点&#xff0c;还是会和Qt冲突。所以现在试试Qt的方式取点&#xff0c;看看能不能实现我的功能。 查了很多资料&#xff0c;这篇博文里的实例有部分参考了祥知道-CSDN博客这位博主的博客[Q…

【Ant Design of Vue】Modal.confirm无法关闭的bug

一、问题 在使用 Ant Design Vue 的 Modal.confirm 确认框时&#xff0c;出现了点击取消和确定后 Modal.confirm 确认框无法关闭的问题 二、代码 代码完全是 copy 的官网的代码&#xff0c;但是 copy 到本地后就会出现上述问题 <template><a-button click"sho…

基于gd32f103移植freemodbus master 主栈

1.移植freemodbus master需要先移植RT-Thread操作系统 GD32F103C8T6移植 RTT Nano 教程-CSDN博客 2.移植freemodbus master协议栈 在移植了RTT以后,我们需要移植就只有串口相关的函数 移植freemodbus master协议栈具体步骤 下载移植freemodbus master协议栈 源码添加协议栈…

ora-12154无法解析指定的连接标识符

用户反映查询的时候报错ora-12154 这个系统只做历史数据查询使用&#xff0c;使用并不平凡&#xff0c;该数据库曾做过一次服务器间的迁移。 用户描述&#xff0c;所有oracle客户端查询该视图都报tns错误&#xff0c;一般ora-12154会发生在连接数据库时&#xff0c;因为tns配…

Python数据分析案例36——基于神经网络的AQI多步预测(空气质量预测)

案例背景 不知道大家发现了没&#xff0c;现在的神经网络做时间序列的预测都是单步预测&#xff0c;即(需要使用X的t-n期到X的t-1期的数据去预测X的t期的数据)&#xff0c;这种预测只能预测一个点&#xff0c;我需要预测X的t1期的数据就没办法了&#xff0c;有的同学说可以把预…

Vue 3 hooks的基本使用及疑问

前言 vue3也用过一段时间了&#xff0c;hooks听说过&#xff0c;但是一直没有用过。公司的前端项目里也没有相应的应用&#xff0c;因此打算系统的学习一下。 hooks与普通函数的区别 以实现一个加法功能为例。 普通函数未抽离 <template><div class"box&quo…

【Vue】Vue 路由的配置及使用

目录捏 前言一、路由是什么&#xff1f;1.前端路由2.后端路由 二、路由配置1.安装路由2.配置路由 三、路由使用1.route 与 router2. 声明式导航3. 指定组件的呈现位置 四、嵌套路由&#xff08;多级路由&#xff09;五、路由重定向1.什么是路由重定向&#xff1f;2.设置 redire…

接口自动化测试框架设计

文章目录 接口测试的定义接口测试的意义接口测试的测试用例设计接口测试的测试用例设计方法postman主要功能请求体分类JSON数据类型postman内置参数postman变量全局变量环境变量 postman断言JSON提取器正则表达式提取器Cookie提取器postman加密接口签名 接口自动化测试基础getp…

JVM实战(28)——模拟Metaspace内存溢出

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

15.云原生之k8s容灾与恢复实战

云原生专栏大纲 文章目录 Velero与etcd介绍Velero与etcd备份应用场景Velero与etcd在k8s备份上的区别 Velero备份恢复流程备份工作流程Velero备份时&#xff0c;若k8s集群发送变化&#xff0c;会发发生情况&#xff1f;Velero 备份pv&#xff0c;pv中数据变化&#xff0c;会发发…

C++ //练习 1.25 借助网站上的Sales_item.h头文件,编译并运行本节给出的书店程序。

C Primer&#xff08;第5版&#xff09; 练习 1.25 练习 1.25 借助网站上的Sales_item.h头文件&#xff0c;编译并运行本节给出的书店程序。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /********************************…

Flutter中使用minio_new库

前言 在移动开发中&#xff0c;我们常常会遇到需要在App中处理文件上传和下载的需求。Minio是一个开源的对象存储服务&#xff0c;它兼容Amazon S3云存储服务接口&#xff0c;可以用于存储大规模非结构化的数据。 开始之前 在pubspec.yaml文件中添加minio_new库的依赖&#xf…

最终Docker6:nacos集群部署

目录 mysql容器构建 1.进入soft 文件夹&#xff0c;创建mysql文件夹 2.进入conf文件夹 放入my.conf 配置文件 3.运行mysql容器 4.进入script文件夹 导入 sql文件 5.进入mysql 容器 并登录 6.创建nacos 数据库并使用&#xff0c;运行nacos.sql文件 7.授予用户所有权限 部…

loading stable diffusion model: FileNotFoundError解决方案

大家好&#xff0c;我是水滴~~ 本文主要介绍在安装 stable-diffusion-webui 时出现的 loading stable diffusion model: FileNotFoundError 问题的解决方案&#xff0c;希望能对你有所帮助。 文章目录 问题描述解决方案 问题描述 在安装 stable-diffusion-webui 过程中出现 l…

Linux环境下,针对QT软件工程搭建C++Test单元测试环境的操作指南

文章目录 前言一、安装QT二、安装CTest三、使用QT生成.bdf文件四、创建CTest工程注意事项 前言 CTest是Parasoft公司出品的一款可以针对C/C源代码进行静态分析、单元测试、集成测试的测试工具。本文主要讲解如何在Linux环境下&#xff0c;搭建QT插件版的CTest测试环境。 一、…

大数据开发之Hadoop(优化新特征)

第 1 章&#xff1a;HDFS-故障排除 注意&#xff1a;采用三台服务器即可&#xff0c;恢复到Yarn开始的服务器快照。 1.1 集群安全模块 1、安全模式&#xff1a;文件系统只接收读数据请求&#xff0c;而不接收删除、修改等变更请求 2、进入安全模式场景 1&#xff09;NameNod…