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

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: 0System.out.println("limit: "+byteBuffer.limit());      // limit: 10System.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 4System.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为1System.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(); // 读取了0byteBuffer.get(); // 读取了1System.out.println("positon: "+byteBuffer.position()); //positon: 2byteBuffer.compact(); // 缓存区压缩已读数据,转为写模式,此时缓冲区还有2 3 4 三个数据System.out.println("positon: "+byteBuffer.position()); //positon: 3byteBuffer.put((byte) 99); //positon: 4byteBuffer.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.mzph.cn/news/631143.shtml

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

相关文章

Zabbix分布式监控系统概述、部署、自定义监控项、邮件告警

目录 前言 &#xff08;一&#xff09;业务架构 &#xff08;二&#xff09;运维架构 一、Zabbix分布式监控平台 &#xff08;一&#xff09;Zabbix概述 &#xff08;二&#xff09;Zabbix监控原理 &#xff08;三&#xff09;Zabbix 6.0 新特性 1. Zabbix server高可用…

10- OpenCV:基本阈值操作(Threshold)

目录 1、图像阈值 2、阈值类型 3、代码演示 1、图像阈值 &#xff08;1&#xff09;图像阈值&#xff08;threshold&#xff09;含义&#xff1a;是将图像中的像素值划分为不同类别的一种处理方法。通过设定一个特定的阈值&#xff0c;将像素值与阈值进行比较&#xff0c;根…

BEESCMS靶场小记

MIME类型的验证 image/GIF可通过 这个靶场有两个小坑&#xff1a; 1.缩略图勾选则php文件不执行或执行出错 2.要从上传文件管理位置获取图片链接&#xff08;这是原图上传位置&#xff09;&#xff1b;文件上传点中显示图片应该是通过二次复制过去的&#xff1b;被强行改成了…

路由器的妙用:使用无线路由器无线桥接模式充当电脑的无线网卡

文章目录 需求说明第一步&#xff1a;重置、连接路由器第二步&#xff1a;设置无线桥接模式第三步&#xff1a;电脑连接路由器上网 需求说明 在原路由无线覆盖的范围内&#xff0c;使用无网卡台式和其他主机&#xff0c;并且有闲置的无线路由器或者网线太短&#xff0c;可以使…

添加边界值分析测试用例

1.1创建项目成功后会自动生成封装好的函数&#xff0c;在这些封装好的函数上点击右键&#xff0c;添加边界值分析测试用例&#xff0c;如下图所示。 1.2生成的用例模版是不可以直接运行的&#xff0c;需要我们分别点击它们&#xff0c;让它们自动生成相应测试用例。如下图所示&…

nas-群晖docker查询注册表失败解决办法(平替:使用SSH命令拉取ddns-go)

一、遇到问题 群晖里面的docker图形化界面现在不能直接查询需要下载的东西&#xff0c;原因可能就是被墙了&#xff0c;那么换一种方式使用SSH命令下载也是可以的&#xff0c;文章这里以在docker里面下载ddns-go为例子。 二、操作步骤 &#xff08;一&#xff09;打开群晖系统…

《Redis:NoSQL演进之路与Redis深度实践解析》

文章目录 关于NoSQL为什么引入NoSQL1、单机MySQL单机年代的数据库瓶颈 2、Memcached&#xff08;缓存&#xff09; MySQL 垂直拆分 &#xff08;读写分离&#xff09;3、分库分表水平拆分MySQL集群4、如今的网络架构5、总结 NoSQL的定义NoSQL的分类 Redis入门Redis能干嘛&…

原生SSM整合(Spring+SpringMVC+MyBatis)案例

SSM框架是Spring、Spring MVC和MyBatis三个开源框架的整合&#xff0c;常用于构建数据源较简单的web项目。该框架是Java EE企业级开发的主流技术&#xff0c;也是每一个java开发者必备的技能。下面通过查询书籍列表的案例演示SSM整合的过程. 新建项目 创建文件目录 完整文件结…

google网站流量怎么获取?

流量是一个综合性的指标&#xff0c;可以说做网站就是为了相关流量&#xff0c;一个网站流量都没有&#xff0c;那其实就跟摆饰品没什么区别 而想从谷歌这个搜索引擎里获取流量&#xff0c;一般都分为两种方式&#xff0c;一种是网站seo&#xff0c;另一种自然就是投广告&#…

线程的使用

线程的创建方式 1、实现Runnable Runnable规定的方法是run()&#xff0c;无返回值&#xff0c;无法抛出异常 实现Callable 2、Callable规定的方法是call()&#xff0c;任务执行后有返回值&#xff0c;可以抛出异常 3、继承Thread类创建多线程 继承java.lang.Thread类&#xff0…

C++ //练习 1.15 编写程序,包含第14页”再探编译“中讨论的常见错误。熟悉编译器生成的错误信息。

C Primer&#xff08;第5版&#xff09; 练习 1.15 练习 1.15 编写程序&#xff0c;包含第14页”再探编译“中讨论的常见错误。熟悉编译器生成的错误信息。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /******************…

[足式机器人]Part2 Dr. CAN学习笔记-Ch04 Advanced控制理论

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - Ch04 Advanced控制理论 1. 绪论2. 状态空间表达State-Space Representation3. Phase Portrait相图&#xff0c;相轨迹3 1. 1-D3 2. 2-D3 3. General Form3 4. Summary3.5. 爱情中的数学-Phase …

.NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接

Flurl.Http-3.2.4 升级到 4.0.0 版本后&#xff0c;https请求异常&#xff1a;Call failed. The SSL connection could not be established. 如下图&#xff1a; Flurl.Http-3.2.4版本绕过https的代码&#xff0c;对于 Flurl.Http-4.0.0 版本来说方法不再适用&#xff0c;3.2.…

【萤火虫系列教程】3/5-Adobe Firefly 创意填充

003-Adobe Firefly 创意填充 创意填充 登录账号后&#xff0c;在主页点击创意填充的【生成】按钮&#xff0c;进入到创意填充页面 我们可以上传自己的图像 一键抠图 点击【背景】就可以把主图抠出来 点击【反转】就可以把背景抠出来 点击【清除】就可以恢复到图片原来…

表的增删改查 进阶(一)

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;MySql&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 数据库约束 约束类型 NOT NUll 约束 UNIQUE 约束 D…

K8S对外服务ingress

Sevice作用体现在两个方面 集群内部 不断跟踪pod的变化&#xff0c;更新endpoint中的pod对象&#xff0c;基于pod的ip地址不断发现的一种服务发现机制 集群外部 类似负载均衡器&#xff0c;把流量&#xff08;ip端口&#xff09;&#xff0c;不涉及转发url&#xff08;http ht…

STM32WLE5JC介绍

32位 ARM Cotrex-M4 CPU 32MHz晶体振荡器 32 kHz RTC振荡器与校准 20x32位备份寄存器 引导程序支持USART和SPI接口 介绍 STM32WLE5/E4xx远程无线和超低功耗器件嵌入了强大的超低功耗LPWAN兼容无线电解决方案&#xff0c;支持以下调制&#xff1a;LoRa&#xff0c;&#xff08…

设计模式-简单工厂

设计模式-简单工厂 简单工厂模式是一个集中管理对象创建&#xff0c;并根据条件生成所需类型对象的设计模式&#xff0c;有助于提高代码的复用性和维护性&#xff0c;但可能会导致工厂类过于复杂且违反开闭原则。 抽象提取理论&#xff1a; 封装对象创建过程解耦客户端与产品…

读书笔记-《数据结构与算法》-摘要8[桶排序]

桶排序和归并排序有那么点点类似&#xff0c;也使用了归并的思想。大致步骤如下&#xff1a; 设置一个定量的数组当作空桶。Divide - 从待排序数组中取出元素&#xff0c;将元素按照一定的规则塞进对应的桶子去。对每个非空桶进行排序&#xff0c;通常可在塞元素入桶时进行插入…

JAVA SECS发送Report C#处理SECS Report SECS发送事件资料大全 S6F11 建立通讯S1F13

发送S6F11非常简单&#xff0c;只需5~6行代码&#xff0c;最核心是代码清晰易懂。 任何人都可以一看就能上手&#xff0c;如果说用代码可读性作为不可替代性的壁垒就无话可说了。 private void buttonS6F11_Click(object sender, EventArgs e) {int nTransaction 0;// 数据部…