Java 入门指南:Java NIO —— Buffer(缓冲区)

news2024/9/29 11:37:30

NIO 的引入

在传统的 Java I/O 模型(BIO)中,I/O 操作是以阻塞的方式进行的。当一个线程执行一个 I/O 操作时,它会被阻塞直到操作完成。这种阻塞模型在处理多个并发连接时可能会导致性能瓶颈,因为需要为每个连接创建一个线程,而线程的创建和切换都是有开销的。

为了解决这个问题,在 Java1.4 版本引入了 NIO(New I/O or Non-Blocking I/O)java.nio。提供了一种基于缓冲区、选择器和非阻塞 IO 模型的 IO 处理方式。相比于之前的 BIO 模型,NIO 可以实现更高的并发、更低的延迟以及更少的资源消耗。

I/O 包和 NIO 已经很好地集成了,java.io 也已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。

![[BIO vs NIO.png]]
Java NIO 概要介绍:初识 Java NIO

使用 NIO 并不一定意味着高性能,它的性能优势主要体现在高并发和高延迟的网络环境下。当连接数较少、并发程度较低或者网络传输速度较快时,NIO 的性能并不一定优于传统的 BIO 。

Buffer

在 NIO(New Input/Output)模型中,Buffer 是一个重要的概念,与数据打交道,用于在内存中存储数据通过 Channel 将数据传输到 Buffer 缓冲区中,并在缓冲区内进行数据的读写操作

Buffer 本质上是一个数组,可以存储多个相同类型的基本数据类型,如 byte、short、int、long、float、double 等。Buffer 封装了内部的数组,并提供了一些操作该数组的方法。

NIO 提供了多种 Buffer 类型,如

  • ByteBuffer(最常用的 Buffer 类)
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

每种 Buffer 类型对应着不同的基本数据类型,用于存储不同类型的数据。

核心变量

Buffer 除了存储数据外,还保存着四个关键属性:

  1. capacity:表示 Buffer 的容量,即可以存储的最大数据量,容量在创建 Buffer 时就确定,之后不可修改。

  2. position:表示当前 Buffer 中已经处理的数据位置,初始值为 0,必须手动设置。每当进行读写操作时,Position 会自动由相应的 get()put() 函数更新,向前移动

  3. limit:表示 Buffer 中可供访问的数据的最大位置(上界),初始值为 capacity,可以手动设置为 position 或其他值。

  4. mark:表示一个备忘记录,用于在某个特定位置设置 mark,然后在之后的某个时间点通过调用 reset() 方法恢复到该 position。

在进行数据读写操作时,一般需要调用 put() 方法将数据写入 Buffer 中,或者调用 get() 方法从 Buffer 中读取数据。

对于写入操作,由于 position 属性的存在,保证了写入的数据不会覆盖已有的数据;对于读取操作,由于 position 属性的存在,保证了读取的数据不会超出可访问的数据范围。

使用 Buffer 进行数据读写时,需要注意处理好 position 和 limit 属性的值,以及不同类型的 Buffer 之间的数据转换问题。Buffer 并不是线程安全的,使用时需要注意线程同步的问题

常用方法

  1. put():向 Buffer 中写入数据。put() 方法有多个重载形式,它们可以将不同类型的数据写入 Buffer 中。例如 put(byte b)、putInt(int i)、putFloat(float f) 等。

    • put(type value):将指定类型的数据写入到 Buffer 中,position 会自动向前移动。

    • put(byte[] array):将 byte 数组从当前 position 处写入 Buffer,同时会增加 position 的值。

    • put(ByteBuffer src):将 src 中的剩余字节写入 Buffer,同时会增加 position 的值。

  2. get():从 Buffer 中读取数据。get() 方法也有多个重载形式,根据不同的数据类型可以选择对应的 get() 方法进行读取。例如,getInt()、getFloat()、getChar() 等。

    • get():从当前 position 处读取一个字节,并将 position 向前移动。

    • get(byte[] array):将从当前 position 处开始的字节序列读入给定的 byte 数组中,并增加 position 的值。

    • get(ByteBuffer dst):将从当前 position 处开始的字节序列读入给定的 ByteBuffer 中,并增加 position 和 dst 的 position 值。

  3. flip():设置 Buffer 的 limit 属性 为 当前的 position,然后将 position 属性设置为 0。在写入数据后,调用 flip() 方法可以将 Buffer 切换到读模式。

  4. rewind():将 position 属性设置为 0,不改变 limit 属性,可以重复读取 Buffer 中的数据。

  5. clear():将 Buffer 清空,position 和 limit 属性设置为初始值。可以重复写入 Buffer 中的数据。

  6. compact():将 position 属性设置为 Buffer 中未处理数据的下一个位置,将未读取的数据移到缓冲区头部,以便更多数据写入。limit 属性则表示缓冲区尾部未处理空间的末尾位置。

  7. mark():用于设置一个备忘位置

  8. reset():用于恢复到 mark() 所标记的位置。

  9. capacity():返回 Buffer 的容量,即可以存储的最大数据量。

  10. position():返回当前 Buffer 中的位置(position)。

  11. position(int newPostition):设置 Buffer 的 Position

  12. limit():返回 Buffer 的上界(limit),表示 Buffer 中可供访问的数据的最大位置。

  13. limit(int newLimit):设置 Buffer 的 limit

  14. remaining():返回剩余可读取或可写入的元素数量,即 l i m i t − p o s i t i o n limit - position limitposition

  15. hasRemaining():检查是否还有剩余可读取或可写入的元素。

  16. isReadOnly():检查 Buffer 是否为只读缓冲区。

  17. array():返回 Buffer 所支持的数组,如果 Buffer 不支持数组,则抛出 UnsupportedOperationException 异常。

  18. duplicate():创建一个与原 Buffer 共享相同数据的新 Buffer。

  19. slice():创建一个新 Buffer,与原 Buffer 共享相同数据,但通过修改其 position、limit 和 mark 属性来表示一个更小的数据集合。

  20. compact():将未读取的数据移到 Buffer 的开头,同时将 position 设置为未读取数据的结尾,便于继续写入数据。

  21. wrap(byte[] array, int offset, int length):将一个字节数组或指定范围的字节数组包装成一个 ByteBuffer 对象。

    offset:包装的起始位置。length:包装的长度。二者可以为空

    使用 ByteBuffer.wrap() 方法可以方便地将字节数组转换为 ByteBuffer 对象,从而可以进行更方便的读取和写入操作。需要注意的是,通过 wrap() 方法包装的 ByteBuffer 对象和原始的字节数组共享内存空间,对其中一个的修改会影响到另一个。

ByteBuffer

ByteBuffer 是 Java NIO 中的一个缓冲区(Buffer)类,用于在内存中存储字节数据。它是一个抽象类,并且是 Buffer 类最常用的子类,提供了操作字节数据的方法。

获取和设置当前字节顺序

ByteBuffer 类中的 order() 方法用于获取或设置字节顺序(Byte Order),字节顺序指的是在多字节数据存储中,高字节放在哪个位置。在 Java NIO 中,ByteBuffer 中的数据存储都是以大端字节顺序(Big Endian)进行存储的,即高位字节存放在低位地址处,而低位字节存放在高位地址处

如果需要改变字节顺序,在 ByteBuffer 实例化后,可以通过调用 order() 方法设置字节顺序。如果需要将其切换到小端字节顺序(Little Endian),可以通过传入 ByteOrder.LITTLE_ENDIAN 常量作为参数来实现,否则,默认情况下字节顺序仍为大端字节顺序。

// 获取当前 ByteBuffer 的字节序 
ByteOrder order = byteBuffer.order(); 

// 设置 ByteBuffer 的字节序为小端字节序 
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

直接缓冲区与非直接缓冲区

在 Java NIO 中,缓冲区(Buffer)是一块连续的内存区域,用于在 Java 程序和底层 I/O 之间传输数据。Java NIO 提供了两种类型的缓冲区:直接缓冲区(Direct Buffer)和非直接缓冲区(Non-direct Buffer)。

  • 非直接缓冲区存储在 JVM 内部,数据需要从应用程序(Java)复制到非直接缓冲区,再复制到内核缓冲区,最后发送到设备(磁盘/网络)。

  • 对于直接缓冲区,数据可以直接从应用程序(Java)复制到内核缓冲区,无需经过 JVM 的非直接缓冲区。

![[HeapByteBuffer&DirectByteBuffer.png]]

直接缓冲区
  • 直接缓冲区使用了操作系统的内存,通过 ByteBuffer.allocateDirect(int capatity) 方法创建。

  • 直接缓冲区的创建和销毁比较慢,但在 I/O 操作中的性能一般较好,特别适合大量的数据传输。

  • 由于直接缓冲区使用了堆外内存,因此对于频繁的 I/O 操作,可以减少数据复制的过程,提高读写效率。

  • 直接缓冲区的内存分配和销毁不受 Java 堆大小的影响,但是由于使用了本地内存,可能会导致内存消耗较大。因此,在使用直接缓冲区时,需要谨慎使用和及时释放。

非直接缓冲区
  • 非直接缓冲区使用了 JVM 堆 内存,通过 ByteBuffer.allocate(int capatity) 或其他分配方法创建。

  • 非直接缓冲区的创建和销毁速度相对较快,因为它是在 Java 堆上分配的内存。

  • 非直接缓冲区的 I/O 性能相对较差,因为在进行 I/O 操作时,还需要进行数据复制,增加了数据复制的开销。

  • 由于使用了 Java 堆内存,因此受到堆大小的限制,当 Java 堆内存较小或已用内存较大时,可能会导致内存不足或频繁的垃圾回收。

MappedByteBuffer

MappedByteBuffer 是 Java NIO 中的一个特殊类型的缓冲区,用于表示一个内存映射文件,将文件的一部分或全部映射到内存中。它与文件 NIO 通道(FileChannel)相关联,并且只能通过 Channel 的 文件通道(FileChannel)创建。

MappedByteBuffer 可以让文件直接在内存(堆外内存)中进行修改,通过直接操作内存来实现对文件的读写,而不需要将文件从磁盘上复制到一个缓冲区中,使得操作文件的 I/O 性能得到提高。

特点

MappedByteBuffer 有以下几个特点:

  1. 只能通过 FileChannel 创建。

  2. 需要将文件映射到内存中才能进行数据的读写。

  3. 映射区域的大小不能超过 Integer.MAX_VALUE

  4. 修改缓冲区中的数据也会修改文件中的数据。

构造方法

MappedByteBuffer 可以通过 FileChannel 的 map 方法来创建,该方法返回一个新的 MappedByteBuffer,并映射到指定文件的指定区域:

FileChannel.map(FileChannel.MapMode.mode, 
				int position,
				int size)
  • mode:表示是只读模式(READ_ONLY)、读写模式(READ_WRITE)或专用模式(PRIVATE)。

  • position:表示从文件的哪个位置开始映射。

  • size:表示映射到内存的字节数,即缓冲区的容量。

映射到的缓冲区可以进行修改,并且对文件进行修改,这种修改是直接写入到文件,因此修改后的内容将立即反映到文件中。不过需要注意的是,如果写入的数据超过了映射区域的大小,则会抛出异常。

force()

MappedByteBuffer.force() 方法将缓冲区中的修改刷新到磁盘上,保持文件和内存的一致性。

Scatter 和 Gather

Java IO 中的 ScatterGather 是一种 I/O 模式,用于在网络或磁盘 I/O 操作时改善性能。它们在 NIO(New IO)中引入,并在 Java 1.4 中加入。ScatterGather 模式通过将 I/O 操作中的散乱数据块收集到一个连续的缓冲区中(Gather),或者将一个连续的数据块分散到不同的缓冲区中(Scatter),来减少数据挪动和复制的次数,从而提高了性能。

Scatter

Scatter 模式下,它将从一个 Channel 读取的数据分散(写入)到多个缓冲区。这种操作可以在读取数据时将其分散到不同的缓冲区,有助于处理结构化数据。

例如,我们可以将消息头、消息体和消息尾分别写入不同的缓冲区。

这种模式常用于将数据分发到多个不同的缓冲区,并且数据也可以从多个通道读入到一个缓冲区中。

// 分散读取数据到多个缓冲区

ByteBuffer headerBuffer = ByteBuffer.allocate(128);
ByteBuffer bodyBuffer = ByteBuffer.allocate(1024);

ByteBuffer[] buffers = {headerBuffer, bodyBuffer};

long bytesRead = socketChannel.read(buffers);

// 输出缓冲区数据
headerBuffer.flip();
while (headerBuffer.hasRemaining()) {
    System.out.print((char) headerBuffer.get());
}

System.out.println();

bodyBuffer.flip();
while (bodyBuffer.hasRemaining()) {
    System.out.print((char) bodyBuffer.get());
}
Gather

Gather 模式下,与 Scatter 相反,它将多个缓冲区中的数据聚集(读取)并写入到一个 Channel。这种操作允许我们在发送数据时从多个缓冲区中聚集数据。

例如,我们可以将消息头、消息体和消息尾从不同的缓冲区中聚集到一起并写入到同一个 Channel

这种模式常用于自动化多路复用和数据重新组合。

// 聚集数据从多个缓冲区写入到 Channel

ByteBuffer headerResponse = ByteBuffer.wrap("Header Response".getBytes());
ByteBuffer bodyResponse = ByteBuffer.wrap("Body Response".getBytes());

ByteBuffer[] responseBuffers = {headerResponse, bodyResponse};

long bytesWritten = socketChannel.write(responseBuffers);

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

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

相关文章

Java-数据结构-时间和空间复杂度 (ಥ_ಥ)

目录: 一、算法效率: 1、我们如何衡量一个算法的好与坏: 2、算法效率: 二、时间复杂度: 1、概念: 2、大O的渐进表示法: 3、推导大O渐进方法: 4、时间复杂度的举例&#xff1…

【推荐】Linux 推荐软件

【推荐】Linux 推荐软件 星火应用商店 Spark-Store: 专注Linux应用适配的应用商店 专注Linux应用适配的应用商店 微信 基于wine工具;wine中的windows涉及很多DLL需要配置,可以借助winetricks、Q4wine,另外还需要一个windwos系统,用来复制其中…

私人诊所|基于SprinBoot+vue的私人诊所管理系统(源码+数据库+文档)

私人诊所管理系统 基于SprinBootvue的私人诊所管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员功能实现 患者功能实现 医生功能实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&am…

el-table自定义合并表格

前沿 : 为了更好的展示数据,很多地方用到表格合并,但是element文档里面没有好的合并方法,只能自定义合并表格来解决需求。于是乎,写了以下方法,方面以后拿来即用。 自定义合并表格 表格数据 tableData: [{i…

图片怎么裁剪中间部分?这几种裁剪方法每个人都学的会!

图片怎么裁剪中间部分?在数字生活的广阔图景中,图片裁剪作为一项基本技能,其重要性日益凸显,这一操作不仅是对图像边界的精准界定,更是通往个性化表达与标准化应用的桥梁。从日常社交媒体的瞬间分享,到专业…

多模态工业异常检测算法整理

本文统计了MVTec 3D-AD上的多个多模态异常检测算法,仅对比其I-AUROC指标。数据的来源为多模态工业异常检测Benchmark | Ziuch の Blog,这位博主经常分享很多工业异常检测的优秀博文,质量很高。 MVTec 3D-AD相关的异常检测算法包含3大类&#…

智慧公厕系统,重塑公共卫生间新生态@卓振思众

在快节奏的现代生活中,公共卫生间作为城市基础设施的重要组成部分,其管理效率和使用体验直接关系到市民的生活质量。近年来,随着科技的飞速发展,智慧卫生间系统应运而生,以其智能化、便捷化的特点,正逐步改…

低代码用户中心的构建与应用

引言 在现代软件开发中,低代码平台因其高效、灵活、用户友好的特性而逐渐受到青睐。特别是在用户中心的构建方面,低代码平台能够显著提升开发效率,降低开发成本。本文将探讨如何利用低代码平台构建一个高效的用户中心,并分享一些…

树形dp + 位运算 + 差分,MC0362 异或

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 码题集OJ-异或 (matiji.net) 二、解题报告 1、思路分析 考虑每个结点u&a…

工作 6 年,@Transactional 注解用的一塌糊涂

接手新项目一言难尽,别的不说单单就一个 Transactional 注解用的一塌糊涂,五花八门的用法,很大部分还失效无法回滚。 有意识的在涉及事务相关方法上加Transactional注解,是个好习惯。不过,很多同学只是下意识地添加这个…

Redis的ZSet底层数据结构

一、ZSet底层数据结构 typedef struct zset{// 跳表zskiplist *zsl;// 字典dict *dic; }zset类型的底层数据结构是由压缩列表或跳表、**字典(哈希表)**实现的。 如果zset中元素个数小于128个,并且每个元素的值小于64字节时,redi…

Prompt提示词如何写才能发挥大语言模型LLM的最大潜力

提示词 提示工程学是一门相对较新的学科,用于开发和优化提示,以便高效地利用语言模型(LM)来进行各种应用和研究主题。提示工程技能有助于更好地了解大型语言模型(LLM)的能力和限制。研究人员使用提示工程来…

错误处理与日志记录:在自动化脚本中实施的有效策略

目录 引言 错误处理机制 1. 异常捕获与处理 2. finally子句 3. 异常信息的打印输出 日志记录 1. 使用logging模块 基本配置 日志级别 日志回滚 2. 自定义日志格式 3. 多处理器和过滤器 实践案例 自动化测试中的错误处理与日志记录 脚本示例 结论 在自动化测试领…

载流子的产生与复合

文章目录 前言有三种形式能够产生载流子 前言 半导体中能贡献导电作用的电子和空穴称为载流子 有三种形式能够产生载流子 热激发(本征激发):价带中的电子受到热激发可能会跃迁至导带,成为能够起导电作用的电子,同时对…

给自己复盘用的tjxt笔记day12第一部分

优惠券使用 优惠券规则定义 对优惠券的下列需求: 判断一个优惠券是否可用,也就是检查订单金额是否达到优惠券使用门槛 按照优惠规则计算优惠金额,能够计算才能比较并找出最优方案 生成优惠券规则描述,目的是在页面直观的展示各种方案,供用户选择 因此,任何一张优惠券都…

SpringWeb 重定向

现在前端后分离:如何确认是跳转到前端页面还是后端的方法呢?RedirectView:重定向如何区分重定向的是前端页面还是后端的一个controller呢 先看下:SpringBoot系列教程web篇之重定向-阿里云开发者社区 ## 根据浏览器中返回的状态码…

vue3中,vue-echarts基本使用(关系图、知识图谱、柱状图、饼图、折线图)

vue3 安装vue-echarts npm i -S vue-echarts echarts//cnpm 安装 cnpm i -S vue-echarts echartsvue2 注意:Vue 2 下使用 vue-echarts,必须还要安装 @vue/composition-api : npm i -D @vue/composition-api //cnpm 安装 cnpm i -D @vue/composition-api main.js中全局注册…

【支付】PayPal支付通道注册(中国大陆、香港)

PayPal支付通道分个人版和企业版,在注册和功能以及收费上都有所区别,如果在测试阶段个人版也有sandbox账户(包括Client ID和Secret),如果要切换到生产环境,会让你升级为企业版,只有企业版才可以…

华清远见元宇宙实验中心,开启嵌入式、物联网与人工智能教学新篇章

2024年8月23日,在北京举行的“匠心服务智启新程”2025新品发布会上,华清远见教育科技集团向行业展示了其最新的科技成果。其中最引人注目的焦点之一,莫过于元宇宙实验中心的发布。 正值华清远见教育科技集团20周年的里程碑时刻,这…

【零知识证明】构建第一个zk

1 必要步骤 视频学习:5. Circcom 中的基本算术电路_哔哩哔哩_bilibili 文字学习:https://hackmd.io/YlNLZS2ESI21OSqdTW_mPw/S1jqN-h80/edit 第五课,circom实践,需要安装 1 vscode 2 rust:Windows安装Rust环境&…