【源码解析】Java NIO 包中的 MappedByteBuffer
文章目录
- 1. 前言
- 2. MappedByteBuffer
- 3. 例子
- 4. 属性
- 5. 构造器
- 6. mappingOffset、mappingAddress、mappingLength
- 7. isLoaded 判断内存是否还在内存中
- 8. load 方法将 ByteBuffer 加载到 Page Cache 中
- 9. force 刷盘
1. 前言
上一篇文章我们介绍了 HeapByteBuffer 的源码,这篇文章我们来介绍下 MappedByteBuffer,这个 MappedByteBuffer 是 DirectByteBuffer 的父类。
- 【源码解析】Java NIO 包中的 Buffer
- 【源码解析】Java NIO 包中的 ByteBuffer
- 【源码解析】Java NIO 包中的 HeapByteBuffer
2. MappedByteBuffer
MappedByteBuffer 是 ByteBuffer 的子类,表示一个直接字节缓冲区,其内容是文件的内存映射区域。通过 MappedByteBuffer,程序可以直接对文件内容进行读写操作,而无需通过传统的 I/O 流或通道。
相比传统的文件 IO 操作,比如 FileInputStream 和 FileOutputStream,这种方式可以直接对内存中的数据进行操作,操作系统会负责将内存中的更改同步到磁盘文件中。
MappedByteBuffer 通过 FileChannel 的 map 方法创建,创建的时候可以设置三种模式:
- MapMode.READ_ONLY:只读模式,映射的缓冲区是只读的
- MapMode.READ_WRITE:读写模式,映射的缓冲区是可读写的,对缓冲区的修改会同步到文件中
- MapMode.PRIVATE:私有模式,映射的缓冲区是可写的,但修改不会同步到文件中,而是创建一个私有副本
但是由于 MappedByteBuffer 使用的是堆外内存,所以如果尝试映射过大的文件,可能会导致内存不足(OutOfMemoryError),毕竟内存映射文件的大小受操作系统和可用物理内存的限制。
所以最后总结一下,当需要频繁读写大文件,或者需要随机文件访问的时候就可以使用这个 MappedByteBuffer。
3. 例子
首先我们需要生成一个 1G
的文件。
public class FileTest {public static void main(String[] args) {String filePath = "D:\\学习资料\\计算机编程语言java学习\\后台\\JDK源码\\jdk1.8Source\\src\\test\\file\\hello.txt"; // 生成的文件路径long fileSizeInBytes = 1024L * 1024 * 1024; // 1GBtry {generateFile(filePath, fileSizeInBytes);System.out.println("文件生成成功!路径: " + filePath);} catch (IOException e) {System.err.println("文件生成失败: " + e.getMessage());}}/*** 生成指定大小的文件,内容为 "helloWorld" 的重复填充** @param filePath 文件路径* @param fileSizeInBytes 文件大小(字节)* @throws IOException 如果写入失败*/public static void generateFile(String filePath, long fileSizeInBytes) throws IOException {// "helloWorld" 的字节数byte[] content = "helloWorld".getBytes();int contentLength = content.length;try (FileOutputStream fos = new FileOutputStream(filePath);BufferedOutputStream bos = new BufferedOutputStream(fos)) {// 写入次数long len = fileSizeInBytes / contentLength;// 一次写入 helloWorld 字节数for (long i = 0; i < len; i++) {bos.write(content);}// 剩余字节long remainingBytes = fileSizeInBytes % contentLength;if (remainingBytes > 0) {bos.write(content, 0, (int) remainingBytes);}}}
}
生成了 hello.txt 之后,可以看下面图。
生成 1G 的文件之后我们再来看下传统的 IO 读取数据和 MappedByteBuffer 读取数据的效率。
public class MappedByteBufferPerformance {public static void main(String[] args) throws Exception {String filePath = "D:\\学习资料\\计算机编程语言java学习\\后台\\JDK源码\\jdk1.8Source\\src\\test\\file\\hello.txt"; // 生成的文件路径long fileSize = 1024 * 1024 * 1024; // 1GBlong startTime = System.currentTimeMillis();try (RandomAccessFile file = new RandomAccessFile(filePath, "r");FileChannel channel = file.getChannel()) {MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);// 读取文件内容while (buffer.hasRemaining()) {buffer.get(); // 读取一个字节}}long endTime = System.currentTimeMillis();System.out.println("MappedByteBuffer 读取时间: " + (endTime - startTime) + " ms");}
}
首先上面是 MappedByteBuffer 的读取,总共用了 317 ms,如下图所示。
下面我们再来看下使用传统 IO 来读取文件的耗时。
public class BufferedIOPerformance {public static void main(String[] args) throws Exception {String filePath = "D:\\学习资料\\计算机编程语言java学习\\后台\\JDK源码\\jdk1.8Source\\src\\test\\file\\hello.txt"; // 生成的文件路径long startTime = System.currentTimeMillis();try (FileInputStream fis = new FileInputStream(filePath);BufferedInputStream bis = new BufferedInputStream(fis)) {// 读取文件内容while (bis.read() != -1) {// 读取一个字节}}long endTime = System.currentTimeMillis();System.out.println("普通 I/O 读取时间: " + (endTime - startTime) + " ms");}
}
普通 IO 的读取耗时如下:
所以这里总结下读取的结果:
操作方式 | 文件大小 | 读取时间 | 备注 |
---|---|---|---|
MappedByteBuffer | 1 GB | 317 ms | 直接内存映射,效率极高 |
BufferedInputStream | 1 GB | 19552 ms | 带缓冲区的普通 I/O,速度较慢 |
4. 属性
MappedByteBuffer 中只有一个属性 fd,其他属性都在父类 ByteBuffer 中。
private final FileDescriptor fd;
是 Java 中用于表示操作系统文件描述符的对象。它允许 Java 程序与底层的文件系统进行交互。说白了这玩意就是用来映射文件到内存的。
5. 构造器
MappedByteBuffer(int mark, int pos, int lim, int cap, // package-privateFileDescriptor fd)
{super(mark, pos, lim, cap);this.fd = fd;
}MappedByteBuffer(int mark, int pos, int lim, int cap) {super(mark, pos, lim, cap);this.fd = null;
}
这两个构造器其实就是一个指定了文件描述符,一个没有指定。
6. mappingOffset、mappingAddress、mappingLength
private long mappingOffset() {// 页大小int ps = Bits.pageSize();// 求直接内存的偏移量long offset = address % ps;// 确保一定是正数return (offset >= 0) ? offset : (ps + offset);
}private long mappingAddress(long mappingOffset) {// address 表示缓冲区的起始地址// mappingOffset 是上面的偏移量return address - mappingOffset;
}private long mappingLength(long mappingOffset) {return (long)capacity() + mappingOffset;
}
第一个方法 mappingOffset
获取的是 MappedByteBuffer 的内存地址相对于内存页面起始位置的偏移量, Bits.pageSize():
这里面返回的是操作系统的内存分页大小,一般是 4KB 或者 8KB,这里取决于用什么操作系统。在进行内存映射的时候可以用这个方法求出偏移量来进行内存对齐。
第二个方法 mappingAddress
用来计算内存页面的起始地址,这里的 mappingOffset
一般就是上面的 mappingOffset
方法。address - mappingOffset
这个方法就是使用缓冲区 ByteBuffer 的起始地址减去偏移量。
第三个方法 mappingLength
求出的是内存映射文件的总长度,也就是 mmap 文件映射的内容区域。
上面这几个方法就是获取 MappedByteBuffer 的各种地址信息,那为什么又要有一个偏移量呢?我们知道操作系统分配最小单位是一个页,所以当使用 mmap 映射操作系统内存的时候分配的内存总是一个页的起始位置。
虽然我们获取了 MappedByteBuffer,但是这个 MappedByteBuffer 的起始位置有可能不是一个页的起始位置,也就是说上面图中 mappingAddress
是页的起始位置,但是 MappedByteBuffer 里面的起始地址是 address
。操作系统分配内存肯定是一个页来分配的,所以 MappedByteBuffer 的起始地址和实际分配的有可能不一样,相差就是 mappingOffset
。上面的 mappingOffset
求出来的就是 mappingAddress -> address
之间的距离,而 mappingAddress
求出来的就是操作系统内核实际调用 mmap 分配的内存页起点,就是上图的 mappingAddress
,最后一个方法 mappingLength
求出来的就是 mmap 实际分配的内存容量。
7. isLoaded 判断内存是否还在内存中
public final boolean isLoaded() {// 判断 MappedByteBuffer 有没有映射到一个文件checkMapped();// 如果起始地址为 0 或者容量为 0if ((address == 0) || (capacity() == 0))// 表示已经不在物理内存里面了return true;// 获取 mmap 分配的内存的起始位置,也就是图中的 mappingOffsetlong offset = mappingOffset();// MappedBuffer 实际映射的内存区域大小 也是 mmap 实际分配的内存大小long length = mappingLength(offset);// mappingAddress(offset) 获取实际的映射起始位置 mapPosition// Bits.pageCount(length) 表示分配了多少个页// 调用 native 方法return isLoaded0(mappingAddress(offset), length, Bits.pageCount(length));
}private native boolean isLoaded0(long address, long length, int pageCount);
- 如果结果是 true,表示缓冲区的内容很可能已经驻留在物理内存中,访问这些数据时不会触发虚拟内存页错误或 I/O 操作。
- 如果返回 false,并不一定表示缓冲区的内容没有驻留在物理内存中,可能只是部分数据不在物理内存中。
8. load 方法将 ByteBuffer 加载到 Page Cache 中
public final MappedByteBuffer load() {// 判断文件描述符是不是空checkMapped();if ((address == 0) || (capacity() == 0))return this;// 获取 mmap 内存地址到 MappedByteBuffer 的距离long offset = mappingOffset();// 获取 mmap 分配的内存长度long length = mappingLength(offset);// 调用 native 将 MappedByteBuffer 中的内容预读到 page cache 中load0(mappingAddress(offset), length);// Read a byte from each page to bring it into memory. A checksum// is computed as we go along to prevent the compiler from otherwise// considering the loop as dead code.Unsafe unsafe = Unsafe.getUnsafe();// 一个页的大小int ps = Bits.pageSize();// 这个 ByteBuffer 分配了多少个页int count = Bits.pageCount(length);// 获取 mmap 映射地址的起始地址long a = mappingAddress(offset);byte x = 0;// 从 mmap 起始地址开始遍历所有页,每遍历一次访问一下都会发生缺页中断,// 同时将 MappedByteBuffer 和 Page Cache 进行页表映射for (int i=0; i<count; i++) {x ^= unsafe.getByte(a);a += ps;}if (unused != 0)unused = x;return this;
}
这个方法会将 ByteBuffer 内容里面的数据加载到 Page Cache 中,并且这个方法还会遍历所有页预读一次。因为数据加载到 Page Cache 之后,并不会立刻就生成虚拟内存到物理内存的映射。所以加载到 Page Cache 的物理页之后需要访问一次发生缺页中断,这时候才会生成页表项。
9. force 刷盘
public final MappedByteBuffer force() {checkMapped();if ((address != 0) && (capacity() != 0)) {// 核心逻辑,从 mmap 映射的起始位置开始,将映射的内容进行刷盘long offset = mappingOffset();force0(fd, mappingAddress(offset), mappingLength(offset));}return this;
}private native void force0(FileDescriptor fd, long address, long length);
这个方法就是将 Buffer 里面的数据进行刷盘。
如有错误,欢迎指出!!!
相关文章:
【源码解析】Java NIO 包中的 MappedByteBuffer
文章目录 1. 前言2. MappedByteBuffer3. 例子4. 属性5. 构造器6. mappingOffset、mappingAddress、mappingLength7. isLoaded 判断内存是否还在内存中8. load 方法将 ByteBuffer 加载到 Page Cache 中9. force 刷盘 1. 前言 上一篇文章我们介绍了 HeapByteBuffer 的源码&#…...
【Docker系列】容器内目录显示异常的解决之道
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
echarts:dataZoom属性横向滚动条拖拽不生效
问: 拖拽的过程中,第一次向右拖拽正常,然后就报错: echarts报错: var pointerOption pointerShapeBuilder[axisPointerType](axis,pixeValue,otherExtent),(axis,pixeValue,otherExtent)下划线红色报错:…...
25/1/12 算法笔记 剖析Yolov8底层逻辑
YOLOv8 是一种基于深度学习的目标检测和图像分割模型,属于 YOLO(You Only Look Once)系列的最新版本。YOLO 系列模型以其高效的实时目标检测能力而闻名,YOLOv8 在此基础上进行了一些优化和改进。 Yolov8的主要特点: …...
Python双指针
双指针 双指针:在区间操作时,利用两个下标同时遍历,进行高效操作 双指针利用区间性质可以把 O ( n 2 ) O(n^2) O(n2) 时间降低到 O ( n ) O(n) O(n) 反向扫描 反向扫描: l e f t left left 起点,不断往右走&…...
1、docker概念和基本使用命令
docker概念 微服务:不再是以完整的物理机为基础的服务软件,而是借助于宿主机的性能。以小量的形式,单独部署的应用。 docker:是一个开源的应用容器引擎,基于go语言开发的,使用时apache2.0的协议。docker是…...
数据结构与算法之链表: LeetCode 92. 反转链表 II (Ts版)
反转链表 II https://leetcode.cn/problems/reverse-linked-list-ii/description/ 描述 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left < right请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 示例 1 输入&…...
【PPTist】插入形状、插入图片、插入图表
一、插入形状 插入形状有两种情况,一种是插入固定的形状, 一种是插入自定义的形状。 插入固定的形状时,跟上一篇文章 绘制文本框 是一样一样的,都是调用的 mainStore.setCreatingElement() 方法,只不多传的类型不一…...
三台Centos7.9中Docker部署Redis集群
Docker部署Redis集群 1. 安装 Docker 和 Docker Compose安装 Docker:安装 Docker Compose: 2. 配置 Redis 容器和网络3. 启动 Redis 容器4. 设置 Redis 集群4.1 集群创建异常处理 5. 验证和测试总结 如果 CentOS 服务器上还没有安装 Docker 和 Docker Co…...
Entity 的材质(棋盘、条纹、网格)
Entity 的材质 普通物体的材质 import { nextTick, onMounted, ref } from vue import * as Cesium from cesium // console.log(Cesium, Cesium)const viewer ref<any>(null)onMounted(() > { ... })let material Cesium.Color.YELLOW.withAlpha(0.5)Cesium.Colo…...
MACPA:fMRI连接性分析的新工具
摘要 不同脑区的共同激活为它们之间的功能交互或连接提供了一个有价值的衡量指标。元分析连接模型(MACM)是一种经过充分验证的研究某一特定区域共激活模式的方法,该方法对基于任务的功能磁共振成像(task-fMRI)数据进行种子点(seed-based)元分析。虽然MACM是一种强大…...
JavaScript-一份你的前端入门说明书(计算机专业)
一.简介 1.起源 JavaScript 起源于 1995 年,当时它主要是为了满足网页交互的需求而被创建。它最初的设计目的是为了让网页开发者能够在网页中添加一些简单的交互效果和动态内容。在那个时期,网页大多是静态的,而 JavaScript 的出现为网页带来了新的活力。Netscape 公司的 B…...
STM32供电参考设计
STM32供电参考设计 在图中有VDD,VSS和VDDA,VSSA两种类型的供电引脚,其数据手册解释如下: 令我不解的是:VDDA和VSSA必须分别连接到VDD和VSS,这是什么意思?有大佬能够解答一下吗?…...
python+fpdf:创建pdf并实现表格数据写入
目录 创建pdf文件对象 新增页 添加自定义字体 设置字体 设置文字颜色和背景色 插入内容 换行 插入图片 保存pdf 完整代码 安装:pip install fpdf 创建pdf文件对象 from fpdf import FPDF, Alignpdf FPDF() # 创建pdf文件对象 获取边距 print(pdf.l_…...
亚远景-ASPICE评估:汽车软件项目的过程能力评价
ASPICE(Automotive SPICE)的评估对象主要是汽车软件研发过程。 这个评估过程不仅仅关注最终的软件产品,而是深入到软件开发的全生命周期中,从需求分析、设计、编码、测试到发布和维护等各个环节。 具体来说,ASPICE评…...
电脑提示directx错误导致玩不了游戏怎么办?dx出错的解决方法
想必大家都有过这样的崩溃瞬间:满心欢喜打开心仪的游戏,准备在虚拟世界里大杀四方或者畅游冒险,结果屏幕上突然弹出个 DirectX 错误的提示框,紧接着游戏闪退,一切美好戛然而止。DirectX 作为 Windows 系统下游戏运行的…...
【13】制作镜像以及重启实例
制作镜像 k8s集群 有两个镜像需要制作,一个是master节点,一个是node节点。 在master节点上成功部署了k8s的控制平面,在node节点上部署了worker节点的配置,不知道打包镜像重启之后集群的状态是什么样的。 确认集群在运行&#…...
electron 启动警告
1. 问题 当启动 electron 时,控制台警告 Electron Security Warning (Insecure Content-Security-Policy) This renderer process has either no Content Security 2. 解决方法 在主进程文件 main.js 中添加如下内容 process.env["ELECTRON_DISABLE_SECURI…...
wow-agent 学习笔记
wow-agent-课程详情 | Datawhale 前两课比较基础,无笔记 第三课 阅卷智能体这一块,曾经做过一点和AI助教相关的内容,也是用了一个prompt去进行CoT,但是风格和课程中的不太相同,在下面附上我的prompt 你是一名资深教…...
使用Cilium/eBPF实现大规模云原生网络和安全
大家读完觉得有帮助记得关注和点赞!!! 目录 抽象 1 Trip.com 云基础设施 1.1 分层架构 1.2 更多细节 2 纤毛在 Trip.com 2.1 推出时间表 2.2 自定义 2.3 优化和调整 2.3.1 解耦安装 2.3.2 避免重试/重启风暴 2.3.3 稳定性优先 2…...
“深入浅出”系列之C++:(4)回调函数
在写项目的时候遇见一个问题,现在的需求是主项目需要拿到子项目的结果来进行显示,那么如何集成呢,子项目里面有一个MainWindow类,类里 回调函数是一种通过函数指针将函数作为参数传递给另一个函数的编程技术。这种机制允许程序在特…...
Mysql--运维篇--主从复制和集群(主从复制I/O线程,SQL线程,二进制日志,中继日志,集群NDB)
一、主从复制 MySQL的主从复制(Master-Slave Replication)是一种数据冗余和高可用性的解决方案,它通过将一个或多个从服务器(Slave)与主服务器(Master)同步来实现。主从复制的基本原理是&#…...
设计模式 行为型 状态模式(State Pattern)与 常见技术框架应用 解析
状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态改变时改变其行为,使得对象看起来好像修改了它的类。这种设计模式的核心思想是将对象的状态和行为封装成不同的状态类,通过状态对象的行为改变来避免…...
计算机网络 (38)TCP的拥塞控制
前言 TCP拥塞控制是传输控制协议(Transmission Control Protocol,TCP)避免网络拥塞的算法,是互联网上主要的一个拥塞控制措施。 一、目的 TCP拥塞控制的主要目的是防止过多的数据注入到网络中,使网络能够承受现有的网络…...
鸿蒙面试 2025-01-09
鸿蒙分布式理念?(个人认为理解就好) 鸿蒙操作系统的分布式理念主要体现在其独特的“流转”能力和相关的分布式操作上。在鸿蒙系统中,“流转”是指涉多端的分布式操作,它打破了设备之间的界限,实现了多设备…...
【关于for循环的几种写法】
关于for循环的几种写法 在 C 中,for(int i 0; i < n; i) 是一种常见的循环写法,用于遍历从 0 到 n-1 的索引。如果你希望简化这种写法,可以使用以下几种方法: 1. 使用范围 for 循环 如果你需要遍历一个容器(如数…...
Apache和PHP:构建动态网站的黄金组合
在当今的互联网世界,网站已经成为了企业、个人和机构展示自己、与用户互动的重要平台。而在这些动态网站的背后,Apache和PHP无疑是最受开发者青睐的技术组合之一。这一组合提供了高效、灵活且可扩展的解决方案,帮助您快速搭建出强大的网站&am…...
免费开源的下载工具Xdown
软件介绍 Xdown是一款功能强大的开源免费下载工具,专为PC端用户设计,支持多种协议和下载方式。 1、多线程下载 Xdown支持最高128线程的并发下载,能够将文件分割成多个部分同时下载,从而显著提升下载速度。 2、多种协议支持 该…...
Three.js 数学工具:构建精确3D世界的基石
文章目录 前言一、向量(Vectors)二、矩阵(Matrices)三、四元数(Quaternions)四、欧拉角(Euler Angles)五、颜色(Colors)六、几何体生成器(Geometr…...
如何明智地提问
如何明智地提问的重要总结,让我为主要观点添加一些具体的实践建议: 提问前的准备工作 尝试在 Google、Stack Overflow 等平台搜索相似问题阅读相关文档和错误日志尝试自己调试和排查问题记录下已尝试过的解决方案 选择合适的提问平台 Stack Overflow…...
成都网站建设技术/开发网站
...
高新区网站建设/小程序开发流程
计算机上自动化任务的终极工具就是写程序直接控制键盘和鼠标,这些程序可以控制其他应用,向他们发送虚拟的击键和鼠标点击,就像你自己坐在计算机前与它交互一样,这种技术被称为“图形用户界面自动化”。 GUI自动化的速度非常快&…...
卢松松博客主题 wordpress/深圳最好的外贸seo培训
宁夏十大中职学校一览表2019-09-04 09:45:02文/陶凯月宁夏中职学校有海原县职业中学、泾源县民族职业中学等等,下面就和小编一起了解一下吧,仅供大家参考。宁夏十大中职学校有哪些序号学校名称办学性质学校地址1银川百年农工子弟职业学校民办宁夏回族自治…...
茶陵网站建设/百度收录关键词
一、前言相信我们每个人在SpringMVC开发中,都遇到这样的问题:当我们的代码正常运行时,返回的数据是我们预期格式,比如json或xml形式,但是一旦出现了异常(比如:NPE或者数组越界等等)&…...
网站客服系统软件/哈尔滨seo网站管理
威纶通触摸屏怎么实现批量数据监视(三菱、西门子问题汇总)三菱常见问题解答1:通讯参数是8位数据位,偶校验,1位停止位,波特率19200,无帧头无帧尾,无协议模式,那么D8120多少?答:数据位࿱…...
外国网站备案/软文营销的作用
移动应用程序是每个人生活的重要组成部分,人们可以使用手机应用程序做任何事情。创建最好的应用程序需要更好的技术,Java移动应用程序开发是一种流行的选择,企业为功能丰富的Android应用程序雇佣Java程序员。 首先,Java开发服务对…...