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 {
    @Test
    public 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());
    }


    @Test
    public 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());
    }

    @Test
    public 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());
    }


    @Test
    public 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());
    }

    @Test
    public 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());  // a
        System.out.println("buffer.get() = " + (char) buffer.get());  // b

        System.out.println("buffer.capacity() = " + buffer.capacity());  // 10
        System.out.println("buffer.position() = " + buffer.position());  // 2
        System.out.println("buffer.limit() = " + buffer.limit());        // 4


        System.out.println("----------------------------------");
        buffer.compact();  // 切换写模式


        System.out.println("buffer.capacity() = " + buffer.capacity());  // 10
        System.out.println("buffer.position() = " + buffer.position());  // 2
        System.out.println("buffer.limit() = " + buffer.limit());        // 10


        buffer.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.获取channel
        FileChannel channel = new FileOutputStream("data1.txt").getChannel();

        // 2.获取buffer并填入数据
        String data = "Aomsir";
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(data.getBytes());

        // 3.读取buffer中的内容并写入channel
        channel.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());  // a
        System.out.println("buffer.get() = " + (char) buffer.get());  // b

        buffer.mark();  // 打标记
        System.out.println("buffer.get() = " + (char) buffer.get());  // c
        System.out.println("buffer.get() = " + (char) buffer.get());  // d

        buffer.reset();  // 跳回标记点
        System.out.println("buffer.get() = " + (char) buffer.get());  // c
        System.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奠定坚实的基础

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

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

相关文章

kubernetes prometheus监控

目录 一、部署prometheus 二、 部署nginx监控实例 三、部署prometheus-adapter 一、部署prometheus 清理镜像方便后面一次性上传 docker rmi docker images | grep -v REPOSITORY | awk {print $1":"$2} 删除 docker load -i kube-prometheus-stack-0.58.0.tar…

盘点U-Mail邮件系统安全设计

在当今社会,电子邮件已经成企业沟通和信息传递重要的手段之一,是企业办公中不可或缺的一部分。但是由于企业邮件服务器端口对外开放、企业邮件安全管理能力不足、邮件内容敏感性高等特点,电子邮件也成为了网络攻击者进行网络钓鱼、恶意软件传…

基于Python的pyAV读取H265(HEVC)编码的视频文件

1.问题出现 利用海康威视相机拍出来的视频是H265格式的,相比于常规的H264编码,压缩率更高,但因此如果直接用之前的方法读取,会出现无法读取的情况,如下。 可以看到,对于帧间没有改变的部分&#xf…

AD教程 (十一)封装的统一管理

AD教程 (十一)封装的统一管理 PCB封装添加 一个一个手动添加,效率太低,不建议使用 使用封装管理器快速添加,根据BOM表(元器件清单),修改PCB封装 点击工具,选择封装管理器,进入封装…

全局前置路由守卫(beforeEach)

全局前置路由守卫(beforeEach) 功能:每一次切换任意路由组件之前都会被调用,相当于在进入另一个路由组件之前设置一个权限。 路由守卫的存在意义就是在不同的时间,不同的位置,去添加代码。如:J…

【开源】基于Vue和SpringBoot的生活废品回收系统

项目编号: S 003 ,文末获取源码。 \color{red}{项目编号:S003,文末获取源码。} 项目编号:S003,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&…

黑马程序员微服务SpringCloud实用篇02

SpringCloud实用篇02 0.学习目标 1.Nacos配置管理 Nacos除了可以做注册中心,同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我…

创新无处不在的便利体验——基于智能视频和语音技术的安防监控系统EasyCVR

随着科技的迅猛发展,基于智能视频和语音技术的EasyCVR智能安防监控系统正以惊人的速度改变我们的生活。EasyCVR通过结合先进的视频分析、人工智能和大数据技术,为用户提供了更加智能、便利的安全保护体验,大大提升了安全性和便利性。本文将介…

java数据结构--阻塞队列

目录 一.概念 二.生产者消费者问题 三.阻塞队列接口BlockingQueue 四.基于数组实现单锁的阻塞队列 1.加锁方式 2.代码实现 3.解释说明 (1).offer添加元素 (2)poll取出元素 4.timeout超时时间 5.测试 五.基于数组实现双锁的阻塞队列 1.问题 …

四.pyqt5 登录界面和功能

一.使用qt creator 设置登录界面 主界面为之前设计的界面 from123.py 文章地址:三.listview或tableviw显示 二.导出ui文件为py文件 # from123.py 为导出 py文件 form.ui 为 qt creator创造的 ui 文件 pyuic5 -o x:\xxx\Fromlogin20230809.py form.ui三.python 显…

中断处理程序的延迟可能导致中断标志位仍然被置位

当中断处理程序的执行时间超过了中断事件的频率时,可能出现中断标志位仍然被置位的情况。让我们来详细解释一下这种情况。 在一个典型的系统中,中断处理程序会在中断事件发生时被触发执行。中断处理程序负责处理中断事件,并可能执行一系列操…

mac的可清除空间(时间机器)

看到这个可用82GB(458.3MB可清除) 顿时感觉清爽,之前的还是可用82GB(65GB可清除),安装个xcode都安装不上,费解半天,怎么都解决不了这个问题,就是买磁盘情理软件也解决不了…

网络运维Day06-补充

文章目录 RAID磁盘阵列RAID0条带模式RAID1镜像模式RAID5高性价比模式RAID01RAID10 逻辑卷一块磁盘的使用流程逻辑卷的使用流程 制作逻辑卷步骤一:添加硬盘步骤二:分区规划步骤三:制作物理卷步骤四:制作卷组步骤五:制作…

PHP网站源码 知识付费分站代理自助下单系统花粥商城放墙带知识付费模版

花粥商城,自带防墙,本人一直在用,没有被墙过,自带知识付费模版美化版,用户登录的页面也很好看 上传商城源码,再把知识付费模版上传到根目录 访问域名,后台地址:域名/admin 登录账…

opencv4笔记

图像二值化 全局法Threshold 大津法 大津法OSTU阈值类型——适用于双峰直方图 OTSU算法也称最大类间差法,由大津于1979年提出,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,它是按图…

关于Maven中pom.xml文件不报错但无法导包解决方法

问题 我的pom文件没有报红&#xff0c;但是依赖无法正常导入。 右下角还总出现这种问题。 点开查看报错日志。大致如下 1) Error injecting constructor, java.lang.NoSuchMethodError: org.apache.maven.model.validation.DefaultModelValidator: method <init>()V no…

关于锁策略

常见的锁策略悲观锁乐观锁读写锁轻量级锁、重量级锁自旋锁公平锁和非公平锁可重入锁 vs 不可重入锁synchronized是什么锁呢&#xff1f; 常见的锁策略 锁策略不仅仅限制于Java;其它锁相关的也是会涉及这些策略;这些特性主要是在实现锁的时候运用的。虽然我们的工作可能就是把轮…

virtualBox虚拟机局域网访问配置

在VirtualBox中&#xff0c;桥接网络是一种网络连接类型&#xff0c;它允许虚拟机连接到物理网络上的路由器或交换机&#xff0c;在物理网络上获得独立的网络地址和访问权限。 一、设置VirtualBox桥接网络的步骤&#xff1a; 打开VirtualBox软件&#xff0c;并选择你想要配置…

FPGA运算

算数运算中&#xff0c;输入输出的负数全用补码来表示&#xff0c;例如用三位小数位来表示的定点小数a-1.625和b-1.375。那么原码分别为a6b‘101101, b6b101011, 补码分别是a6’b110011&#xff0c;b6‘b110101&#xff1b; 如果想在fpga中实现a*b&#xff0c;则需要将a和b用补…

文件夹批量改名:实用技巧,如何快速删除文件夹名称中的数字

在我们的日常生活和工作中&#xff0c;文件夹命名经常会包含一些数字&#xff0c;这些数字可能是在文件夹创建时自动添加的&#xff0c;也可能是为了方便我们识别而手动添加的。然而&#xff0c;有时候这些数字可能会变得不再必要&#xff0c;或者我们想要删除它们以使文件夹名…