Netty入门指南之NIO Buffer详解

news2024/12/23 5:44:37

作者简介:☕️大家好,我是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奠定坚实的基础

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

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

相关文章

终究还是翻车了,人肉运维100 次后

翻车现场 5年前的一个晚上,我接到数据组同事的消息,要求将A用户的磁盘快照共享给B用户。我对这个线上运维工作早已轻车熟路,登录线上服务器仅用了2分钟就完成了。 我继续忙着其他事情,3分钟后,我正要打开新的控制台页…

【多线程】synchronized的特性

文章目录 synchronized 的特性互斥可重入synchronized的使用加锁过程 synchronized 的特性 互斥 synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。进入 synchron…

【源码篇】基于SSM+JSP实现的网上花店系统

系统介绍 基于SSMJSP实现的网上花店系统采用了时下流行的 Java 程序设计语言进行开发,系统开发的工具采用 Idea 开发工具,普通用户可以在该系统完成注册、登录、购买等一系列操作,致力于为用户提供一个方便快捷的在线购花平台。 前台系统功…

Oracle(14) Managing Password Security and Resources

目录 一、基础知识 1、Profiles 配置文件 2、Password Management 密码管理 3、Enabling Password Mgmt 启用密码管理 4、Password Verification 密码验证 ​编辑5、User-Provided Passwd Func 用户提供的密码功能 6、Verif Func: VERIFY_FUNCTION验证函数介绍 7、Reso…

Web时代下,软件系统的持续进步,是否能完全替代人力节省成本?

Web时代下,软件系统的持续进步,是否能完全替代人力节省成本? 随着全球经济的蓬勃发展,众多经济学家纷纷提出了新的管理理念,例如在20世纪50年代,西蒙提出管理依赖信息和决策的思想,但在同时期的…

2023年云计算的发展趋势如何?

混合云的持续发展:混合云指的是将公有云和私有云进行结合,形成一种统一的云计算环境。随着企业对数据隐私和安全性的要求越来越高,以及在数据存储和处理方面的需求不断增长,混合云正在逐渐成为主流。预计未来混合云将会继续保持高…

,多数据源+Mybatisplus + Sharding JDBC同一库中分表

水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中,多数据源采用 mybatis-plus的dynamic-datasource 分库分表采用sharding-jdbc 数据库连接池管理是alibaba的druid-spring-boot-starter 同一个数据库内分表 目录 1.数据库表 2.配置 3.引入的…

蓝桥等考C++组别六级004

第一部分:选择题 1、C L6 (15分) 关于switch语句,以下说法正确的是( )。 A. break语句只能用于switch语句。 B. switch语句中可以使用多个default语句。 C. switch语句中只能使用一个break语句。 D. …

优雅的并发编程-CompletableFuture

目录 了解CompletableFuture CompletableFuture 是 Java 8 引入的一个类,用于支持异步编程和非阻塞操作。它提供了一种简单而强大的方式来处理异步任务,可以轻松地实现并行、非阻塞的操作,并且提供了丰富的方法来处理任务的完成状态、异常情…

iOS加固原理与常见措施:保护移动应用程序安全的利器

​ 目录 iOS加固原理与常见措施:保护移动应用程序安全的利器 前言 一、iOS加固的原理 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 二、iOS加固的常见措施 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 三、iOS加固的效果和注意事项 参…

什么是代理IP池?如何判断IP池优劣?

代理池充当多个代理服务器的存储库,提供在线安全和匿名层。代理池允许用户抓取数据、访问受限制的内容以及执行其他在线任务,而无需担心被检测或阻止的风险。代理池为各种在线活动(例如网页抓取、安全浏览等)提高后勤保障。 读完…

一个“Hello, World”Flask应用程序

如果您访问Flask网站,会看到一个非常简单的示例应用程序,只有5行代码。为了不重复那个简单的示例,我将向您展示一个稍微复杂一些的示例,它将为您编写大型应用程序提供一个良好的基础结构。 应用程序将存在于包中。在Python中&…

三大基础排序 -选择排序、冒泡排序、插入排序

排序算法 文章目录 冒泡排序算法步骤动图代码优化总结 选择排序算法步骤动图代码总结 插入排序算法步骤动图代码总结 排序算法,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。一般默认排序是按照由小到大即…

【JVM】运行时数据区、程序计数器

🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaEE 操作系统 Redis 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 JVM 一、 运行时数据区二、 程序计数器程序…

Console 线连接路由器交换机等设备

Console 线连接路由器交换机等设备 Console 线几乎是每一个网工必备的,学会使用 Console 线去连接真实的设备也是非常重要的。这里我们就使用 XShell 软件来演示设备的连接和管理配置。 文章目录 Console 线连接路由器交换机等设备一、Console 线二、连接设备 Cons…

2023-11-Rust

学习方案:Rust程序设计指南 1、变量和可变性 声明变量:let 变量、const 常量 rust 默认变量一旦声明,就不可变(immutable)。当想改变 加 mut(mutable) 。 const 不允许用mut ,只能声明常量,…

详解静态成员变量以及静态成员函数

一、静态成员变量 类的静态成员变量是该类的所有对象共有的(只有一份),只能在类里声明,类外定义。 相当于只属于类的全局变量。 1、定义: 只能在全局中定义 2、访问方式:(假如类A 中有公有静态变量 _a) ,可以用 A::_a 或 A a; a._…

大数据毕业设计选题推荐-市天气预警实时监控平台-Hadoop-Spark-Hive

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

华为eNSP实验-DHCP实验(简易入门版)

1.拓扑图 2.R1配置 <Huawei>system-view [Huawei]sysname R1 [R1]interface GigabitEthernet 0/0/0 [R1-GigabitEthernet0/0/0]ip address 192.168.1.1 24 [R1]ip pool PC [R1-ip-pool-PC]gateway-list 192.168.1.1 [R1-ip-pool-PC]network 192.168.1.0 mask 24 [R1-i…