Java内存映射,上G大文件轻松处理
内存映射文件(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 < randomAccessFile.length(); i++) {
randomAccessFile.seek(i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
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 < size; i++) {
mappedByteBuffer.get(i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
测试程序也很简单,大致如下:
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 项目,所以它很容易导入和运行。
Java内存映射,上G大文件轻松处理的更多相关文章
- Java NIO内存映射---上G大文件处理(转)
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了java中内存映射的原理及过程,与传统IO进行了对比,最后,用实例说明了结果 ...
- 【Web应用】JAVA网络上传大文件报500错误
问题描述 当通过 JAVA 网站上传大文件,会报 500 错误. 问题分析 因为 Azure 的 Java 网站都是基于 IIS 转发的,所以我们需要关注 IIS 的文件上传限制以及 requestT ...
- java上传大文件解决方案
需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在10G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以10G来进行限制. 第一步: 前端修改 由于项目使用的是BJ ...
- 【转】Python之mmap内存映射模块(大文本处理)说明
[转]Python之mmap内存映射模块(大文本处理)说明 背景: 通常在UNIX下面处理文本文件的方法是sed.awk等shell命令,对于处理大文件受CPU,IO等因素影响,对服务器也有一定的压力 ...
- IIS7下swfupload上传大文件出现404错误
要求上传附件大小限制在2G,原本以为可以轻松搞定.在编译模式下可以上传大文件,可是在IIS7下(自己架的服务器),一上传大的文件就会出现 Http 404错误,偶尔有的文件还有IO. error错误. ...
- JS上传大文件的解决方案
最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...
- [Asp.net]Uploadify上传大文件,Http error 404 解决方案
引言 之前使用Uploadify做了一个上传图片并预览的功能,今天在项目中,要使用该插件上传大文件.之前弄过上传图片的demo,就使用该demo进行测试.可以查看我的这篇文章:[Asp.net]Upl ...
- php 上传大文件配置upload_max_filesize和post_max_size选项
php 上传大文件配置upload_max_filesize和post_max_size选项 (2014-04-29 14:42:11) 转载▼ 标签: php.ini upload _files[f ...
- PHP上传大文件 分割文件上传
最近遇到这么个情况,需要将一些大的文件上传到服务器,我现在拥有的权限是只能在一个网页版的文件管理系统来进行操作,可以解压,可以压缩,当然也可以用它来在线编辑.php文件. 文件有40M左右,但是服务器 ...
随机推荐
- JAVA面试题 启动线程是start()还是run()?为什么?
面试官:请问启动线程是start()还是run()方法,能谈谈吗? 应聘者:start()方法 当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它 ...
- js常用设计模式实现(三)建造者模式
创建型模式 创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离 关于创建型模式,已经接近尾声了,还剩下建造者模式和原型模式,这一篇说一说建造者模式 建造者模式的定义 将一个 ...
- synchronized与ReentrantLock实现共享资源的消费
主方法 public class synchronizedTest { public static void main(String[] args) { long startTime = System ...
- 网络设置管理 NetSetMan Pro v4.7.1 Lite 绿色便携版
下载地址:点我 基本介绍 Netsetman是一个小巧好用的工具,你可以设置六组不同的网络参数值,针对不同的网络环境,而调用不同的参数,当你在家中.学校.工作单位等不同环境切换网络配置文件时,只需要通 ...
- .Net Core 创建和使用中间件
1. 定义中间内容 1.1 必须有一个RequestDelegate 委托用了进入一个中间件 1.2 通过构造函数设置这个RequestDelegate委托 1.3 必须有一个方法Task Invok ...
- ASP.NET Core系列(一): .NET Core简介及安装开发环境
大家都知道Java是跨平台的,.NET因为不具有跨平台的特性,被越来越多的开发者诟病,之前有各种间接的跨平台的方案,比如mono.但是由于各种兼容问题,最终 .NET Core出现了,它可以让程序在W ...
- Windows10 OpenSSH 快捷设置 避免 Bad owener or permission on
配置ssh 有两个地方 ~/.ssh/config 这个亲测失败,怎么搞都报错 bad owner .... c:/programdata/ssh/ssh_config 亲测有效 (显示隐藏文件才看的 ...
- 20190719 NOIP模拟测试6 (考后反思)
总分 130 排名第6 虽然与前几次进步了一些,但总会感觉到不安 因为我只是A掉了第一题,而第一题又是道水题,很显然的DP,我相信大佬们没A掉只是因为一些小问题(也许有大佬不屑于这种题吧,lockey ...
- 在dotnet core实现类似crontab的定时任务
前段需要在业务中实现某些时间段的简单定时任务,类似crontab的调度,因为业务会放在docker中,所以不想用直接用crontab,在网上搜了一下,发现一个开源的实现 Pomelo.AspNetCo ...
- WPF依赖属性的正确学习方法
前言 我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴. 相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学 ...