Netty入门指南之NIO Buffer详解

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Netty应用专栏_Aomsir的博客-CSDN博客

文章目录

  • 参考文献
  • 前言
  • ByteBuffer组织结构
  • ByteBuffer的获取方式
  • ByteBuffer核心结构
  • 结构图例演示
    • 1、Buffer初创建
    • 2、Buffer写入部分数据后
    • 3、调用flip读方法
    • 4、调用clear写方法
    • 5、调用compact方法
    • 6、代码演示
  • Buffer有关核心API
    • 写数据进Buffer
      • 从Buffer读数据
      • Channel#write()方法
      • Buffer#rewind()方法
      • Buffer#mark()&reset()方法
  • 字符串操作
    • 字符串存储到Buffer中
  • 总结

参考文献

  • 孙哥suns说Netty
  • Netty官方文档

前言

在上一篇文章中,简单介绍了Buffer是什么,怎么获取Buffer,如何使用Buffer的读写操作等,对于我们NIO的两个核心组件:Channel和Buffer,更为重要的是Buffer,Channel只是建立通道的一个管道,Buffer是实际用来存储数据的。

ByteBuffer组织结构

Buffer是一个抽象类,它有多个抽象子类,包括ByteBufferLongBufferStringCharBuffer等。在这些子类中,我们主要关注ByteBuffer,这是其中一个具体实现的抽象类。ByteBuffer具有两个主要的继承类:MappedByteBufferHeapByteBuffer

MappedByteBuffer类下有一个继承类,名为DirectByteBuffer,代表直接内存,即操作系统内存。而HeapByteBuffer则代表JVM的堆内存。两者之间的区别在于,JVM堆内存上的读写操作效率较低,受垃圾回收的影响,而操作系统的直接内存允许高效的读写操作,但用完不对直接内存进行析构可能会造成内存泄漏。
在这里插入图片描述

ByteBuffer的获取方式

我们可以通过两种方式获取ByteBuffer。第一种方式是使用ByteBuffer的allocate方法创建,这种方式需要在创建时指定大小,一旦分配了大小后,无法动态扩容。第二种方式是使用Charset的encode方法

ByteBuffer.allocate(10);CharsetEncoder.encode()

ByteBuffer核心结构

  • ByteBuff是一个类似数组的结构,整个结构中包含有三个主要的状态
    • Capacity:即Buffer的容量,类似数组的size
    • Position:即Buffer当前缓存的下标,在读取操作时记录读到了哪个位置;在写操作时记录写
    • Limit:读写限制,在读操作时,设置了你能读多少字节的数据;在写操作时,设置你还能写多少字节的数据

所谓的读写模式,是程序相对Buffer的,本质上就是这几个状态的变化。主要有Position和Limit联合决定了Buffer的读写区域数据

注意:刚创建出来的Buffer默认为写模式,代表程序和Channel可以往里面写数据

结构图例演示

上面,我们通过文字方式详细介绍了Netty中ByteBuffer的核心结构。接下来,我将逐步使用图例来讲解这三个核心组件在读写操作时的变化

1、Buffer初创建

ByteBuffer在初始创建时默认为写模式,允许程序和Channel向其中写入数据。此时,Position指向Buffer的最开头,Capacity指向最末尾,而Limit也指向最末尾。Position与Limit之间的这段区间表示了可用于写入数据的有效空间
在这里插入图片描述

2、Buffer写入部分数据后

当我们通过程序或Channel向Buffer中写入部分数据后,如下图所示:Position指向最后一个数据的索引位置,同时Limit和Capacity都位于数据的最后位置
在这里插入图片描述

3、调用flip读方法

在之前的图中,我们往Buffer中写入了四条数据:1、2、3和4。此时,当我们调用flip方法以切换到读模式时,Position会指向Buffer的最开头,而Limit会指向写模式下Position的位置。接下来,我们可以从Buffer中读取数据了。每读取一个数据,Position就会向后移动一个位置,直到与Limit重合
在这里插入图片描述

4、调用clear写方法

当在读模式下从Buffer中读取数据,但还未读取完全就需要切换为写模式时,如果直接使用clear方法,会导致三个指针恢复到初始状态,且未被读取的数据会被直接覆盖。因此,一般情况下我们避免使用clear方法来切换模式,以免丢失未读完的数据
在这里插入图片描述

5、调用compact方法

另一个用于Buffer写模式的方法是compact。当我们从Buffer中读取数据时,如果还未读取到Limit的位置就需要切换为写模式。如果我们使用clear方法切换到写模式,那么Position与Limit之间未被读取的数据将全部丢失,这可能不符合我们的开发需求。因此,我们可以使用compact方法。该方法会将Position与Limit之间未被读取的数据压缩到Buffer的最开始,然后将Position指向未被读取数据的最后索引位置,同时将Limit指向Capacity,以便后续写入操作。
在这里插入图片描述

6、代码演示

public class TestNIO4 {@Testpublic void testState1() {ByteBuffer buffer = ByteBuffer.allocate(10);System.out.println("buffer.capacity() = " + buffer.capacity());System.out.println("buffer.position() = " + buffer.position());System.out.println("buffer.limit() = " + buffer.limit());}@Testpublic void testState2() {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});System.out.println("buffer.capacity() = " + buffer.capacity());System.out.println("buffer.position() = " + buffer.position());System.out.println("buffer.limit() = " + buffer.limit());}@Testpublic void testState3() {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});buffer.flip();  // 切换读模式System.out.println("buffer.capacity() = " + buffer.capacity());System.out.println("buffer.position() = " + buffer.position());System.out.println("buffer.limit() = " + buffer.limit());}@Testpublic void testState4() {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});buffer.clear();  // 切换读模式System.out.println("buffer.capacity() = " + buffer.capacity());System.out.println("buffer.position() = " + buffer.position());System.out.println("buffer.limit() = " + buffer.limit());}@Testpublic void testState5() {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});buffer.flip();  // 切换写模式System.out.println("buffer.get() = " + (char) buffer.get());  // aSystem.out.println("buffer.get() = " + (char) buffer.get());  // bSystem.out.println("buffer.capacity() = " + buffer.capacity());  // 10System.out.println("buffer.position() = " + buffer.position());  // 2System.out.println("buffer.limit() = " + buffer.limit());        // 4System.out.println("----------------------------------");buffer.compact();  // 切换写模式System.out.println("buffer.capacity() = " + buffer.capacity());  // 10System.out.println("buffer.position() = " + buffer.position());  // 2System.out.println("buffer.limit() = " + buffer.limit());        // 10buffer.flip();System.out.println("buffer.get() = " + (char) buffer.get());     // c}
}

Buffer有关核心API

写数据进Buffer

  • Channel的read方法:channel.read(buffer)
  • Buffer的put方法
    • buffer.put(byte)
    • buffer.put(byte[])

从Buffer读数据

  • Buffer的get方法:每调用一次都会影响Position的位置
  • Buffer的get(i)方法,用于获取特定Position上的数据,但是不会对Position产生影响
  • 如下还有三个文件

Channel#write()方法

在上一篇文章中,我们演示了如何使用FileInputStream和FileOutputStream流来执行文件读取和写入操作,通过输入流从文件中获取数据流,并通过输出流将程序中的字节写回文件。然而,在NIO中,我们使用Channel来进行文件操作,而Channel是无方向性的。这意味着我们可以使用Channel的write()方法,从Buffer中读取数据并将其写入文件中。

public class TestNIO11 {public static void main(String[] args) throws Exception{// 1.获取channelFileChannel channel = new FileOutputStream("data1.txt").getChannel();// 2.获取buffer并填入数据String data = "Aomsir";ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(data.getBytes());// 3.读取buffer中的内容并写入channelchannel.write(buffer);}
}

Buffer#rewind()方法

当我们从ByteBuffer中读取数据时,Position指针会逐步向前移动。但如果我们希望重新读取已读取的数据,可以使用rewind方法。该方法将Position指针重置到Buffer的开头,允许我们重新读取数据,如下是rewind的代码和我们的测试案例。
在这里插入图片描述

public class TestNIO5 {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});buffer.flip();while (buffer.hasRemaining()) {System.out.println("buffer.get() = " + (char) buffer.get());}System.out.println("-----------------------------");buffer.rewind();   // 重新获取数据(因为读完以后数据没有删除,只是position和limit重合)while (buffer.hasRemaining()) {System.out.println("buffer.get() = " + (char) buffer.get());}}
}

Buffer#mark()&reset()方法

除了rewind()方法可以将position置为最初状态,如果我们想要重复读取某一个区间的内容,Buffer还提供了两个有用的方法:mark()和reset()。mark()方法可以帮助我们记住当前position的位置,而reset()方法则允许我们后续回退到position的位置,以便重复读取特定区间的数据。

public class TestNIO6 {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10);buffer.put(new byte[]{'a','b','c','d'});buffer.flip();System.out.println("buffer.get() = " + (char) buffer.get());  // aSystem.out.println("buffer.get() = " + (char) buffer.get());  // bbuffer.mark();  // 打标记System.out.println("buffer.get() = " + (char) buffer.get());  // cSystem.out.println("buffer.get() = " + (char) buffer.get());  // dbuffer.reset();  // 跳回标记点System.out.println("buffer.get() = " + (char) buffer.get());  // cSystem.out.println("buffer.get() = " + (char) buffer.get());  // d}
}

字符串操作

字符串存储到Buffer中

将字符串存入ByteBuffer是一项相对简单的任务,可以使用buffer.put(“Aomsir”.getBytes())。然而,这种方式受当前Java文件的字符编码类型影响。如果当前Java文件使用UTF-8字符集,但我们要存入的字符串包含汉字,可能在读取时会出现问题。因此,通常会选择另一种方式创建ByteBuffer,即使用Charset的encode()方法,这种方法允许我们明确指定字符编码集。需要注意的是,encode方法会自动调用flip读方法,不像之前的ByteBuffer.allocate()方法默认是写模式,所以这里无需显式调用flip方法,否则limit和position都会被重置为0。

public class TestNIO8 {public static void main(String[] args) {// 使用指定字符集直接创建Buffer并填入数据ByteBuffer buffer = Charset.forName("UTF-8").encode("aomsir");// 不用切换为读模式,因为上面的encode方法已经调用了,再调用一次就会导致position=0,limit=0// buffer.flip();while (buffer.hasRemaining()) {System.out.println("buffer.get() = " + (char) buffer.get());}buffer.clear();}
}

总结

ByteBuffer是整个NIO体系中的核心组件,今天我们花了一篇文章的时间来深入学习它的结构、读写模式以及常见API等内容。这将为我们未来学习Netty奠定坚实的基础

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

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

相关文章

XSS 跨站点脚本漏洞详解

文章目录 漏洞概述XSS漏洞原理xss漏洞危害xss漏洞验证XSS漏洞分类反射型存储型DOM型 固定会话攻击原理简单xss注入复现 XSS 攻防xss构造方法利用标签符号<>事件响应javascript伪协议其他标签 XSS 变形方式xss防御黑白名单策略输入过滤 案例XSS 盲打 漏洞概述 ​ 跨站点脚…

操作系统 day09(线程)

线程 为什么引入线程 在没引入进程之前&#xff0c;系统中的各个程序只能串行的执行&#xff0c;比如&#xff1a;只能先听歌&#xff0c;再聊QQ。引入进程之后&#xff0c;各个程序可以并发执行&#xff0c;比如&#xff1a;一边听歌&#xff0c;一边聊QQ。但是现在QQ可以一…

gitlab-ce-12.3.5 挖矿病毒及解决方案

前言 最近发现在使用gitlab提交代码的时候总是失败&#xff0c;一访问gitlab还时常报503&#xff0c;于是使用 top 命令查看了内存占用情况&#xff0c;发现了一个git进程内存使用了2.3g&#xff0c;cpu还一直占用300-400%&#xff0c; 以前不知道gitlab还有病毒&#xff0c;只…

IP 地址冲突检测工具

IP 冲突是一个术语&#xff0c;用于表示同一网络或子网中尝试使用相同 IP 地址的两个或多个设备的状态&#xff0c;这可能会导致发往特定主机的通信与其他主机混淆&#xff0c;因为两者都使用相同的 IP&#xff0c;为了避免这种情况&#xff0c;某些主机在发生 IP 冲突时会失去…

电机应用-直流有刷电机

目录 直流有刷电机 工作原理 直流有刷减速电机的重要参数 电路原理与分析 驱动芯片分析 L298N驱动芯片 直流有刷减速电机控制实现 控制速度原理 硬件设计 L298N 野火直流有刷电机驱动板-MOS管搭建板 软件设计1&#xff1a;两个直流有刷减速电机按键控制 开发设计 …

gpt网站资源分享

gpt网站 gpt网站 下面是一个扫码跳转的图片&#xff1a; 里面有3.5和4模型&#xff0c;目前有免费体验&#xff0c;大家可以试试

C++ 之多态(一)

什么是虚函数 在类的定义中&#xff0c;前面有 virtual 关键字的成员函数称为虚函数&#xff1b;virtual 关键字只用在类定义里的函数声明中&#xff0c;写函数体时不用。 class Base {virtual int Fun() ; // 虚函数 };int Base::Fun() // virtual 字段不用在函数体时定义 …

【MySQL】一文学会所有MySQL基础知识以及基本面试题

文章目录 前言 目录 文章目录 前言 一、主流数据库以及如何登陆数据库 二、常用命令使用 三、SQL分类 3.1 存储引擎 四、创建数据库如何设置编码等问题 4.1操纵数据库 4.2操纵表 五、数据类型 六、表的约束 七、基本查询 八、函数 九、复合查询 十、表的内连和外连 十一、索引…

Luatos Air700 改变BL0942串口波特率

LuatOs 改变模块串口波特率思路参照 luatos 改变AIR530串口波特率 BL0942默认串口波特率可以通过SCLK_BPS引脚接3.3V电源设置到9600bps 但如果调整到38400bps需要修改0x19寄存器 bl0942 v1.06版的特殊寄存器说明&#xff0c;注意早期版本特殊寄存器说明存在错误 完整代码 mai…

多路转接(上)——select

目录 一、select接口 1.认识select系统调用 2.对各个参数的认识 二、编写select服务器 1.两个工具类 2.网络套接字封装 3.服务器类编写 4.源文件编写 5.运行 一、select接口 1.认识select系统调用 int select(int nfds, fd_set readfds, fd_set writefds, fd_set ex…

在linux安装单机版hadoop-3.3.6

一、下载hadoop https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/core/hadoop-3.3.6/ 二、配置环境变量 1、配置java环境变量 2、配置hadoop环境变量 export HADOOP_HOME/usr/local/bigdata/hadoop-3.3.6 export HBASE_HOME/usr/local/bigdata/hbase-2.5.6 export JA…

Python进行数据可视化,探索和发现数据中的模式和趋势。

文章目录 前言第一步&#xff1a;导入必要的库第二步&#xff1a;加载数据第三步&#xff1a;创建基本图表第四步&#xff1a;添加更多细节第五步&#xff1a;使用Seaborn库创建更复杂的图表关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Pyth…

Vue 将响应式数据转为普通对象

toRaw&#xff1a;将一个 reactive 生成的响应式数据转为普通对象。 toRaw 适用于&#xff1a;获取响应式数据对应的普通对象&#xff0c;对这个普通对象所有的操作&#xff0c;都不会引起页面的更新。 markRaw&#xff1a;标记一个对象&#xff0c;使其永远不会再成为响应式…

设置区块链节点输出等级为警告级,并把日志存储阈值位100MB并验证;

题目 获取指定区块链节点输出等级为警告级&#xff0c;并设置日志存储阈值位100MB并验证&#xff1b; 操作步骤 1.切换目录 cd nodes/127.0.0.1/node0 2.打开配置文件并修改 vim config.ini warn&#xff1a;警告

初识微服务技术栈

认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构&#xff0c;这些架构之间有怎样的差别呢&#xff1f; 导学&#xff1a; 了解微服务的优缺点&#xff1b;了解微服务架构的演变过程&am…

智能井盖传感器功能,万宾科技产品介绍

在国家治理方面&#xff0c;对社会的治理是一个重要的领域&#xff0c;一定要在推进社会治理现代化过程中提高市政府的管理和工作能力&#xff0c;推动社会拥有稳定有序的发展。在管理过程中对全市井盖进行统一化管理&#xff0c;可能是市政府比较头疼的难题&#xff0c;如果想…

Mybatis(一)

1. Mybatis简介 MyBatis下载地址 1.1 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下&#xff0c;iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github…

Flink(三)【运行时架构】

前言 今天学习 Flink 的一些原理性的东西&#xff0c;比较偏概念&#xff0c;但是十分重要。有人觉得上来框框敲代码才能学到东西&#xff0c;那是狗屁不通的道理&#xff08;虽然我以前也这么认为&#xff09;。个人认为&#xff0c;学习 JavaEE那些框架&#xff0c;你上来就敲…

毅速丨为什么不锈钢材料在金属3D打印中应用广泛

不锈钢材料作为一种常见材料&#xff0c;在金属3D打印中应用广泛&#xff0c;可以说是目前使用率最高的材料&#xff0c;为什么不锈钢大受欢迎&#xff0c;主要由几点原因。 第一、工艺适合性 金属3D打印的工艺&#xff0c;如直接金属激光烧结&#xff08;DMLS&#xff09;或选…