热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

Java内存映射大文件轻松处理

这篇文章主要介绍了Java内存映射大文件轻松处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

前言

内存映射文件(Memory-mapped File),指的是将一段虚拟内存逐字节映射于一个文件,使得应用程序处理文件如同访问主内存(但在真正使用到这些数据前却不会消耗物理内存,也不会有读写磁盘的操作),这要比直接文件读写快几个数量级。

稍微解释一下虚拟内存(很明显,不是物理内存),它是计算机系统内存管理的一种技术。像施了妖法一样使得应用程序认为它拥有连续的可用的内存,实际上呢,它通常是被分隔成多个物理内存的碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

内存映射文件主要的用处是增加 I/O 性能,特别是针对大文件。对于小文件,内存映射文件反而会导致碎片空间的浪费,因为内存映射总是要对齐页边界,最小单位是 4 KiB,一个 5 KiB 的文件将会映射占用 8 KiB 内存,也就会浪费 3 KiB 内存。

java.nio 包使得内存映射变得非常简单,其中的核心类叫做 MappedByteBuffer,字面意思为映射的字节缓冲区。

01、使用 MappedByteBuffer 读取文件

假设现在有一个文件,名叫 cmower.txt,里面的内容是:

沉默王二,一个有趣的程序员

PS:哎,改不了王婆卖瓜自卖自夸这个臭毛病了,因为文章被盗得都怕了。

这个文件放在 /resource 目录下,我们可以通过下面的方法获取到它:

ClassLoader classLoader = Cmower.class.getClassLoader();
Path path = Paths.get(classLoader.getResource("cmower.txt").getPath());

Path 既可以表示一个目录,也可以表示一个文件,就像 File 那样——当然了,Path 是用来取代 File 的。

然后,从文件中获取一个 channel(通道,对磁盘文件的一种抽象)。

FileChannel fileChannel = FileChannel.open(path);

紧接着,调用 FileChannel 类的 map 方法从 channel 中获取 MappedByteBuffer,此类扩展了 ByteBuffer——提供了一些内存映射文件的基本操作方法。

MappedByteBuffer mappedByteBuffer = fileChannel.map(mode, position, size);

稍微解释一下 map 方法的三个参数。

1)mode 为文件映射模式,分为三种:

  • MapMode.READ_ONLY(只读),任何试图修改缓冲区的操作将导致抛出 ReadOnlyBufferException 异常。
  • MapMode.READ_WRITE(读/写),任何对缓冲区的更改都会在某个时刻写入文件中。需要注意的是,其他映射同一个文件的程序可能不能立即看到这些修改,多个程序同时进行文件映射的行为依赖于操作系统。
  • MapMode.PRIVATE(私有), 对缓冲区的更改不会被写入到该文件,任何修改对这个缓冲区来说都是私有的。

2)position 为文件映射时的起始位置。

3)size 为要映射的区域的大小,必须是非负数,不得大于Integer.MAX_VALUE。

一旦把文件映射到内存缓冲区,我们就可以把里面的数据读入到 CharBuffer 中并打印出来。具体的代码示例如下。

CharBuffer charBuffer = null;
ClassLoader classLoader = Cmower.class.getClassLoader();
Path path = Paths.get(classLoader.getResource("cmower.txt").getPath());
try (FileChannel fileChannel = FileChannel.open(path)) {
 MappedByteBuffer mappedByteBuffer = fileChannel.map(MapMode.READ_ONLY, 0, fileChannel.size());

 if (mappedByteBuffer != null) {
  charBuffer = Charset.forName("UTF-8").decode(mappedByteBuffer);
 }

 System.out.println(charBuffer.toString());
} catch (IOException e) {
 e.printStackTrace();
}

由于 decode() 方法的参数是 MappedByteBuffer,这就意味着我们是从内存中而不是磁盘中读入的文件内容,所以速度会非常快。

02、使用 MappedByteBuffer 写入文件

假设现在要把下面的内容写入到一个文件,名叫 cmower1.txt。

沉默王二,《Web全栈开发进阶之路》作者

这个文件还没有创建,计划放在项目的 classpath 目录下。

Path path = Paths.get("cmower1.txt");

具体位置见下图所示。

然后,创建文件的通道。

FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE,
    StandardOpenOption.TRUNCATE_EXISTING)

仍然使用的 open 方法,不过增加了 3 个参数,前 2 个很好理解,表示文件可读(READ)、可写(WRITE);第 3 个参数 TRUNCATE_EXISTING 的意思是如果文件已经存在,并且文件已经打开将要进行 WRITE 操作,则其长度被截断为 0。

紧接着,仍然调用 FileChannel 类的 map 方法从 channel 中获取 MappedByteBuffer。

 MappedByteBuffer mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, 1024);

这一次,我们把模式调整为 MapMode.READ_WRITE,并且指定文件大小为 1024,即 1KB 的大小。然后使用 MappedByteBuffer 中的 put() 方法将 CharBuffer 的内容保存到文件中。具体的代码示例如下。

CharBuffer charBuffer = CharBuffer.wrap("沉默王二,《Web全栈开发进阶之路》作者");
Path path = Paths.get("cmower1.txt");
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE,
  StandardOpenOption.TRUNCATE_EXISTING)) {
 MappedByteBuffer mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, 1024);

 if (mappedByteBuffer != null) {
  mappedByteBuffer.put(Charset.forName("UTF-8").encode(charBuffer));
 }

} catch (IOException e) {
 e.printStackTrace();
}

可以打开 cmower1.txt 查看一下内容,确认预期的内容有没有写入成功。

03、MappedByteBuffer 的遗憾

据说,在 Java 中使用 MappedByteBuffer 是一件非常麻烦并且痛苦的事,主要表现有:

1)一次 map 的大小最好限制在 1.5G 左右,重复 map 会增加虚拟内存回收和重新分配的压力。也就是说,如果文件大小不确定的话,就不太友好。

2)虚拟内存由操作系统来决定什么时候刷新到磁盘,这个时间不太容易被程序控制。

3)MappedByteBuffer 的回收方式比较诡异。

再次强调,这三种说法都是据说,我暂时能力有限,也不能确定这种说法的准确性,很遗憾。

04、比较文件操作的处理时间

嗨,朋友,阅读完以上的内容之后,我想你一定对内存映射文件有了大致的了解。但我相信,如果你是一名负责任的程序员,你一定还想知道:内存映射文件的读取速度究竟有多快。

为了得出结论,我叫了另外三名竞赛的选手:InputStream(普通输入流)、BufferedInputStream(带缓冲的输入流)、RandomAccessFile(随机访问文件)。

读取的对象是加勒比海盗4惊涛怪浪.mkv,大小为 1.71G。

1)普通输入流

public static void inputStream(Path filename) {
 try (InputStream is = Files.newInputStream(filename)) {
  int c;
  while((c = is.read()) != -1) {
  }
 } catch (IOException e) {
  e.printStackTrace();
 }
}

2)带缓冲的输入流

public static void bufferedInputStream(Path filename) {
 try (InputStream is = new BufferedInputStream(Files.newInputStream(filename))) {
  int c;
  while((c = is.read()) != -1) {
  }
 } catch (IOException e) {
  e.printStackTrace();
 }
}

3)随机访问文件

public static void randomAccessFile(Path filename) {
 try (RandomAccessFile randomAccessFile = new RandomAccessFile(filename.toFile(), "r")) {
  for (long i = 0; i 

4)内存映射文件

public static void mappedFile(Path filename) {
 try (FileChannel fileChannel = FileChannel.open(filename)) {
  long size = fileChannel.size();
  MappedByteBuffer mappedByteBuffer = fileChannel.map(MapMode.READ_ONLY, 0, size);
  for (int i = 0; i 

测试程序也很简单,大致如下:

long start = System.currentTimeMillis();
bufferedInputStream(Paths.get("jialebi.mkv"));
long end = System.currentTimeMillis();
System.out.println(end-start);

四名选手的结果如下表所示。

方法 时间
普通输入流 龟速,没有耐心等出结果
随机访问文件 龟速,没有耐心等下去
带缓冲的输入流 29966
内存映射文件 914

普通输入流和随机访问文件都慢得要命,真的是龟速,我没有耐心等待出结果;带缓冲的输入流的表现还不错,但相比内存映射文件就逊色多了。由此得出的结论就是:内存映射文件,上G大文件轻松处理。

05、最后

本篇文章主要介绍了 Java 的内存映射文件,MappedByteBuffer 是其灵魂,读取速度快如火箭。另外,所有这些示例和代码片段都可以在 GitHub 上找到——这是一个 Maven 项目,所以它很容易导入和运行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 学习笔记(34):第三阶段4.2.6:SpringCloud Config配置中心的应用与原理第三阶段4.2.6SpringCloud Config配置中心的应用与原理
    立即学习:https:edu.csdn.netcourseplay29983432482?utm_sourceblogtoedu配置中心得核心逻辑springcloudconfi ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
author-avatar
戊辰冬月半
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有