【源码解析】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…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
