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.hqwc.cn/news/412112.html

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

相关文章

「优选算法刷题」:快乐数

一、题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1&a…

汇聚户外露营产业链一手货源,来深圳户外展,高效对接厂商资源

漫步在静谧之地&#xff0c;与自然融为一体&#xff0c;户外露营逐渐走入人们视野&#xff0c;被越来越多的旅游爱好者所青睐。 2024年3月14-16日&#xff0c;中国&#xff08;深圳&#xff09;国际户外生活露营展览会&#xff08;COSP&#xff09;将在深圳会展中心(福田馆)盛…

Chondrex--Hydroxyproline Assay Kit羟脯氨酸检测试剂盒

胶原&#xff08;collagen&#xff09;既是结缔组织的主要成分&#xff0c;同时也是许多组织细胞外基质中主要的结构蛋白。作为哺乳动物体内含量最高、分布最广的功能性蛋白&#xff0c;胶原约占生物体内蛋白质总量的25&#xff05;至30&#xff05;&#xff0c;某些生物体甚至…

Nginx 简介

1、概念介绍 Nginx ("engine x") 是一个轻量级、高性能的 WEB 服务器软件和反向代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的&#xff0c;第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。其将源代码以类 BSD 许可证的形式发…

实战之-Redis代替session实现用户登录

一、设计key的结构 首先我们要思考一下利用redis来存储数据&#xff0c;那么到底使用哪种结构呢&#xff1f;由于存入的数据比较简单&#xff0c;我们可以考虑使用String&#xff0c;或者是使用哈希&#xff0c;如下图&#xff0c;如果使用String&#xff0c;注意他的value&…

海康visionmaster-VM 嵌入:嵌入用户软件界面的方法

描述 环境&#xff1a;VM4.0.0 VS2015 及以上 现象&#xff1a;将 VM 整体嵌入到客户软件界面中&#xff1f; 解答 将 VM 软件整体嵌入到客户软件中&#xff0c;需要利用 Panel 控件&#xff0c;并且需要先启动 VM 软件&#xff0c;具 体代码如下&#xff1a; C# [DllImport(“…

ARM 1.16

TCP的特点 面向连接 面向连接&#xff0c;是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”&#xff0c;这样能建立可靠的连接。建立连接&#xff0c;是为数据的可靠传输打下了基础。 仅支持单播传输 每条TCP传输连接只能有两个端点&#…

安全帽/反光衣检测AI边缘计算智能分析网关V4如何修改IP地址?

智能分析网关V4是TSINGSEE青犀推出的一款AI边缘计算智能硬件&#xff0c;硬件采用BM1684芯片&#xff0c;集成高性能8核ARM A53&#xff0c;主频高达2.3GHz&#xff0c;INT8峰值算力高达17.6Tops&#xff0c;FB32高精度算力达到2.2T&#xff0c;硬件内置了近40种AI算法模型&…

C++中基类的析构函数为什么要用virtual虚析构函数

直接的讲&#xff0c;C中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说&#xff0c;如果派生类中申请了堆内存空间&#xff0c;并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数&#xff0c;当删除基类指针指向的派生类对象时就不会触发动态…

基于gd32f103移植freemodbus master 主栈

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

随心玩玩(十三)Stable Diffusion初窥门径

写在前面&#xff1a;时代在进步&#xff0c;技术在进步&#xff0c;赶紧跑来玩玩 文章目录 简介配置要求安装部署下载模型启动ui插件安装教程分区提示词插件Adetailer插件提示词的分步采样采样器选择采样器的收敛性UniPC采样器 高分辨率修复 (Hires. fix)图生图ControlNet介绍…

申请开启|成为亚马逊云科技 Community Builder,共建云端社区!

在探索由技术打造的云端世界时&#xff0c;和同行者一起学习&#xff0c;与技术专家共同探讨是开发者成长的最佳助力&#xff01; 亚马逊云科技开发者社区 Community Builders 为技术爱好者和新兴思想领袖提供技术资源、学习和交流机会&#xff0c;帮助开发者探索、分享技术相关…