Java NIO ———— Buffer 缓冲区详解

引言

缓冲区是一个用于特定基本类型的容器。由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类。

Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互。数据从通道存入缓冲区,从缓冲区取出到通道中。

一、创建缓冲区

缓冲区的本质是 数组 ,用于存储不同类型的数据,根据数据类型(boolean 除外),提供了相应类型的缓冲区,如ByteBuffer、IntBuffer等。这些缓冲区的管理方式都是类似的,都是通过 allocate() 方法指定容量并创建缓冲区。

// 创建一个 1 KB 大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);

一般情况下,我们通过 allocate() 方法创建缓冲区,但是在需要高性能的地方,有时候往往需要使用 allocateDirect() 方法。

allocate() 创建非直接缓冲区,allocateDirect() 创建直接缓冲区。

二、缓冲区的四个核心属性

缓冲区的本质实际上是一个数组,最常用的ByteBuffer,本身就是一个 byte[] 数组,根据数据读取的场景,设计者为Buffer 设置了四个核心属性,定义在 Buffer 抽象类中:

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

缓冲区的操作实际上是借由这四个 int 标记来完成的,可以理解为抽象的指针。它们的关系如下:

0 <= mark <= position <= limit <= capacity

position 表示位置,表示当前程序正在操作的数据的下一个索引值。

mark 表示标记,通过 mark() 方法,记录当前数据的索引。可以通过 reset() 重新找到 mark 所指向的数据。

limit 界限,表示缓冲区中可以操作数据的大小,limit 后的数据不能进行读写。

capacity 缓冲区容量,因为缓冲区本身就是数组,因此一旦声明不能改变该值。

2.1 初始的指针状态

假设我们声明了一个 capacity 为 5 的字节缓冲区

ByteBuffer buf = ByteBuffer.allocate(5);

那么,缓冲区的初始状态就是如下图所示:

2.2 当缓冲区中有数据的状态

由于缓冲区独特的构造,在读和写的时候,limit 与 position 指针是有一定区别的。

// 写模式
byteBuffer.put("Tom".getBytes());

// 读模式
byteBuffer.get();

 三、缓冲区的核心方法

3.1 存取数据

缓冲区既然作为数据的容器,必然涉及到数据的存取操作,但要注意,存和取操作不可以连续执行,两个动作之间需要有一个 “翻转” 的操作。

put() 方法将数据放入到缓冲区中;get() 方法从缓冲区中取出数据。

3.2 flip()翻转、rewind()倒带、clear()清空

flip() : 翻转,将缓冲区进行读写切换

rewind() : 倒带,可以将 position 和 limit 回退到上一次操作前。

clear() : 清空缓冲区,官方说明是“clears the buffer”,但详细解释是将 position 和 limit 恢复“出厂设置”,并丢弃 mark。注意,缓冲区中的数据并非清空,只是将两个指针重置,数据处在一种“被遗忘”状态,如果进行 get()操作依然可以取出。同时,clear 执行之后的缓冲区无法通过 rewind() 回退指针。

3.3 mark()标记、reset()定位

mark()方法可以记录当前 position 的位置,并可以通过 reset() 方法恢复到 mark()

3.4 hasRemaining()是否有未读数据、remaining()获取未读数据数量

hasRemaining() 用于判断读模式下的 Buffer 中是否还有未读数据;

remaining() 方法可以返回剩余可操作的元素个数。其值与 limit - position 的差值相等。

3.5 示例程序

从一开始创建一个 Buffer 开始,通过存入、读取数据来观察各个属性:capacity、limit、position、mark 等的变化。

创建:

// 分配 1 KB 大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("=============allocate()===========");
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

存数据:

System.out.println("=============put()===========");
String name = "abcde";
byteBuffer.put(name.getBytes());
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

反转:

System.out.println("============flip()===========");
byteBuffer.flip();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

读数据:

System.out.println("============get()===========");
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst);
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());
System.out.println(new String(dst));

倒带:

System.out.println("============rewind()===========");
byteBuffer.rewind();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

清空:

System.out.println("============clear()===========");
byteBuffer.clear();
System.out.println("position = " + byteBuffer.position());
System.out.println("limit = " + byteBuffer.limit());
System.out.println("capacity = " + byteBuffer.capacity());

标记、定位标记:

ByteBuffer buf = ByteBuffer.allocate(5);
buf.put("abcde".getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2); // get(byte[] dst, int offset, int length)
System.out.println("第一次取出结果:" + new String(dst));
System.out.println("position:" + buf.position());
// mark()标记
buf.mark();
buf.get(dst, 2, 2); // get(byte[] dst, int offset, int length)
System.out.println("第二次取出结果:" + new String(dst));
System.out.println("position:" + buf.position());
// 恢复到mark
buf.reset();
System.out.println("reset 恢复到 mark 位置");
System.out.println("position:" + buf.position());

查询剩余数据:

// remaining() 获取缓冲区中还可以操作的数量
if (buf.hasRemaining()) {System.out.println(buf.remaining());System.out.println("limit - position = " + (buf.limit() - buf.position()));
}

四、直接缓冲区与非直接缓冲区

字节缓冲区要么是 直接缓冲区,要么是 非直接缓冲区

非直接缓冲区属于常规操作,传统的 IO 流和 allocate() 方法分配的缓冲区都是非直接缓冲区,建立在 JVM 内存中。这种常规的非直接缓冲区会将内核地址空间中的内容拷贝到用户地址空间(中间缓冲区)后再由程序进行读或写操作,换句话说,磁盘上的文件在与应用程序交互的过程中会在两个缓存中来回进行复制拷贝。

直接缓冲区绝大多数情况用于显著提升性能,缓冲区直接建立在物理内存(相对于JVM 的内存空间)中,省去了在两个存储空间中来回复制的操作,可以通过调用 ByteBuffer allocateDirect() 工厂方法来创建。直接缓冲区中的内容可以驻留在常规的垃圾回收堆之外,因此它们对应用程序的内存需求量造成的影响可能并不明显。另外,直接缓冲区还可以通过 FileChannel map() 方法将文件直接映射到内存中来创建,该方法将返回 MappedByteBuffer 。

直接或非直接缓冲区只针对字节缓冲区而言。字节缓冲区是那种类型可以通过 isDirect() 方法来判断。

注意!!!直接缓冲区性能虽然好,但是缓冲区直接建立在物理内存中,无法由 GC来释放,可控性差,同时分配和销毁成本很高!在对性能不是特别依赖的场景不建议使用!

 

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

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

相关文章

基本类型理解巩固及补码原理总结

引言 本篇文章属于计算机基础通识&#xff0c;主要讨论&#xff1a;有符号类型、无符号类型的区别&#xff0c;byte、int 等类型的取值范围&#xff0c;最大值最小值的计算公式的由来&#xff0c;原码、反码、补码转换公式。 有符号类型与无符号类型 在 Java 中的八大基本类…

LeetCode(#118)————杨辉三角形

问题描述 给定一个非负整数 numRows&#xff0c;生成杨辉三角的前 numRows 行。 在杨辉三角中&#xff0c;每个数是它左上方和右上方的数的和。 示例: 输入: 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1] ] 实现方法 class Solution {public List<List<Intege…

JVM001_类文件结构

无关性的基石 实现语言无关性的基础是虚拟机和字节码存储格式。Java虚拟机不与任何语言绑定&#xff08;包括Java&#xff09;&#xff0c;它只与‘Class文件’这种特定的二进制文件格式所关联。Class文件中包含了Java虚拟机指令集、符号表以及其它辅助信息。出于安全考虑&…

Maven学习(六)————企业Maven项目最佳实践

引言 在《Maven学习&#xff08;三&#xff09;————Maven核心概念&#xff08;二&#xff09;》中&#xff0c;学到了Maven 继承和 Maven 聚合的概念&#xff0c;这两个概念&#xff0c;解决的问题分别是&#xff1a; 1、解决一些公共依赖统一版本的问题。 2、统一打包部署…

JVM003_属性表

属性表 预备知识 javac -g Xxx.java 在生成class文件的时候生成所有调试信息javap -v Xxx.class 输出附加信息 属性表结构 类型名称数量备注u2attribute_name_index1属性名称索引&#xff0c;指向一个CONSTANT_Utf8_info型常量的索引u4attribute_length1该属性表的长度u1in…

Lombok ——自动化方法生成器

引言 此文为简单的Lombok 演示。 Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具&#xff0c;通过使用对应的注解&#xff0c;可以在编译源码的时候生成对应的方法。简而言之&#xff0c;一句话就是&#xff1a;通过简单的注解…

LeetCode(#26)————删除排序数组中的重复项

题目 给定一个排序数组&#xff0c;你需要在原地删除重复出现的元素&#xff0c;使得每个元素只出现一次&#xff0c;返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 示例 1: 给定数组 nums […

JVM006_类加载的过程

类加载 类加载时机 类加载的过程 新术语 类加载器 简单的理解为将类转换为二进制流的类或接口。 数组的元素类型 数组去掉所有维度的类型。 数组的组件类型 数组去掉一个维度的类型。 基本块 按照控制流拆分的代码块。 1. 加载 加载是类加载过程的一个阶段。加载阶段主…

服务端开发——云服务器的端口转发设置(SSH隧道)

引言 本篇博客介绍端口转发的知识&#xff0c;并详细阐述操作和设置步骤。这是因为在实际工作中&#xff0c;会有很多企业从安全的角度考虑&#xff0c;为线上或重要的服务器设置一个跳板机&#xff08;堡垒机&#xff09;&#xff0c;避免远程开发人员直接操作&#xff0c;是…

Shiro————核心设计思想

引言 以此篇博客为引&#xff0c;开启一个新的专栏分类——Shiro。 之前在工作中有比较快速的学习过Shiro安全框架&#xff0c;但经过一年的荒废&#xff0c;已经不是很熟悉了&#xff0c;通过这个系列&#xff0c;深入研究和学习Shiro的一些知识&#xff0c;填补安全管理方面…

Web应用安全————账号冻结与 Session 实时失效

引言 开篇时说些题外话&#xff0c;最近刚刚被公司CY&#xff0c;不过很快找到了下家&#xff0c;也同时拿到了三家公司的Offer。一周面试下来&#xff0c;总体感觉面试题少了&#xff0c;不过多了上机程序题。新公司是做外包&#xff0c;不过相比于上一家公司&#xff0c;也算…

Web应用安全————Shiro 解决会话固定漏洞

引言 承接上一篇《Web应用安全————账号冻结与 Session 实时失效》关于 session 的学习&#xff0c;本篇博客聚焦如何通过 shiro 解决会话固定导致的漏洞问题。 首先&#xff0c;没怎么接触过应用安全方面的小伙伴可能会发起疑问 - 什么是会话固定&#xff1f; 简单来说&…

Web应用安全————多点登录互斥

引言 在实际生活中&#xff0c;很多网站都做了多点登录互斥的操作&#xff0c;简单来说就是同一个账号&#xff0c;只能在一台电脑上登录&#xff0c;如果有人在其他地方登录&#xff0c;那么原来登录的地方就会自动下线&#xff0c;再进行操作就会弹出登录界面。 实现思路 …

Linux进阶之路————磁盘查询

引言 承接《Linux进阶之路————Linux磁盘分区与挂载》&#xff0c;本文介绍实际生产中对于磁盘的监控和查询。 一、查询磁盘整体使用情况 基本语法&#xff1a; df -h 该命令会显示包括我们手动挂载的磁盘&#xff0c;如果使用 umount 卸载磁盘&#xff0c;那么将不会显示…

Linux进阶之路————CentOS网络配置

引言 Linux在装机后&#xff0c;如果没有特殊配置&#xff0c;会使用动态获取 IP 地址的策略。本文描述了&#xff0c;虚拟机使用网络的拓扑图&#xff0c;以及如何通过配置&#xff0c;将 IP 地址固定下来&#xff0c;不会因为重启而失效。同时可以访问外网地址。 一、NAT模…

Linux进阶之路————进程与服务管理

引言 在Linux 中&#xff0c;每个执行的程序&#xff08;代码&#xff09;都成为一个进程&#xff0c;Linux 为每一个进程分配了一个唯一的 id 号 - PID。 每个进程都会对应一个父进程&#xff0c;而这个父进程可以复制多个子进程&#xff0c;例如 www 服务器。 每个进程都可…

Linux进阶之路———— RPM 与 YUM 包管理

引言 rpm 是一种用于互联网下载的打包及安装工具&#xff0c;它包含在某些 Linux 发行版中&#xff0c;生成具有 .rpm 扩展名的文件。rpm 是 redhat package manager&#xff08;RedHat 软件包管理器&#xff09;的缩写&#xff0c;类似 Windows 下的 setup.exe 文件。这一文件…

Linux进阶之路———Shell 编程入门

引言 通过 Shell 编程的学习&#xff0c;铺平架构师道路上的一块大砖。 Shell 在Linux 系统中的定位如下所示&#xff1a; 一、第一个 Shell 脚本 我们通过一个简单的 Shell 脚本来感受一下。 在 Shell 中不需要加 “;” 结尾&#xff0c;通过 vim 可以进行 shell 的编程工…

Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8

引言 本篇博客也属于Linux进阶系列&#xff0c;主要讲解如何在CentOS 6 下安装并配置 JDK 8。由于通过 yum 搜索的结果都是 openjdk&#xff0c;而目前企业中还是以 Oracle jdk 为主&#xff0c;因此&#xff0c;操作步骤这样的。 在Oracle 官网把 jdk 1.8 下载下来&#xff…

Linux 实操———CentOS 6 安装配置 Tomcat

引言 Linux下安装Tomcat。 一、下载、传输与解压 同《Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8》一样&#xff0c;前期都是先在远程机上下载压缩包&#xff0c;然后通过远程终端&#xff0c;将压缩包放在 Linux 的 opt 目录下&#xff0c;然后解压。 下载地址是T…