使用Java内存映射(Memory-Mapped Files)处理大文件

简介: NIO中的内存映射 (1)什么是内存映射文件 内存映射文件,是由一个文件到一块内存的映射,可以理解为将一个文件映射到进程地址,然后可以通过操作内存来访问文件数据。说白了就是使用虚拟内存将磁盘的文件数据加载到虚拟内存的内存页,然后就可以直接操作内存页数据。

NIO中的内存映射

(1)什么是内存映射文件
内存映射文件,是由一个文件到一块内存的映射,可以理解为将一个文件映射到进程地址,然后可以通过操作内存来访问文件数据。说白了就是使用虚拟内存将磁盘的文件数据加载到虚拟内存的内存页,然后就可以直接操作内存页数据。
我们读写一个文件使用read()和write()方法,这两个方法是调用系统底层接口来传输数据,因为内核空间的文件页和用户空间的缓冲区没有一一对应,所以读写数据时会在内核空间和用户空间之间进行数据拷贝,在操作大量文件数据时会导致性能很低,使用内存映射文件可以非常高效的操作大量文件数据。
通过内存映射机制操作文件比使用常规方法和使用FileChannel读写高效的多。
内存映射文件使用文件系统建立从用户空间到可用文件系统页的虚拟内存映射,这样做有以下好处:

  • 用户进程把文件数据当内存数据,无需调用read()或write()
  • 当用户进程接触到映射内存空间,会自动产生页错误,从而将文件数据从磁盘读到内存;若用户空间进程修改了内存页数据,相关页会自动标记并刷新到磁盘,文件被更新
  • 操作系统的虚拟内存对内存页进行高速缓存,自动根据系统负载进行内存管理
  • 用户空间和内核空间的数据总是一一对应,无需执行缓冲区拷贝
  • 大数据的文件使用映射,无需消耗大量内存即可进行数据拷贝

(2)如何创建内存映射文件

1
2
3
4
RandomAccessFile raf =  new  RandomAccessFile( "test.txt" "rw" );
FileChannel fc = raf.getChannel();
//将test.txt文件所有数据映射到虚拟内存,并只读
MappedByteBuffer mbuff = fc.map(MapMode.READ_ONLY,  0 , fc.size());

(3)MappedByteBuffer API

MappedByteBuffer是ByteBuffer的子类,所以可被通道读写。MappedByteBuffer提供的方法:
load():加载整个文件到内存
isLoaded():判断文件数据是否全部加载到了内存
force():将缓冲区的更改刷新到磁盘

 

>>读取大文件

下面的测试转自 Java中用内存映射处理大文件 

在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.

如下为一个对比实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import  java.io.BufferedInputStream;
import  java.io.FileInputStream;
import  java.io.FileNotFoundException;
import  java.io.IOException;
import  java.io.RandomAccessFile;
import  java.nio.MappedByteBuffer;
import  java.nio.channels.FileChannel;
 
public  class  Test {
 
 
public  static  void  main(String[] args) {
try  {
FileInputStream fis= new  FileInputStream( "/home/tobacco/test/res.txt" );
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
try  {
while ((n=fis.read())>= 0 ){
sum+=n;
}
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
try  {
FileInputStream fis= new  FileInputStream( "/home/tobacco/test/res.txt" );
BufferedInputStream bis= new  BufferedInputStream(fis);
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
try  {
while ((n=bis.read())>= 0 ){
sum+=n;
}
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
MappedByteBuffer buffer= null ;
try  {
buffer= new  RandomAccessFile( "/home/tobacco/test/res.txt" , "rw" ).getChannel().map(FileChannel.MapMode.READ_WRITE,  0 1253244 );
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
for ( int  i= 0 ;i< 1253244 ;i++){
n= 0x000000ff &buffer.get(i);
sum+=n;
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
}
 
}

测试文件为一个大小为1253244字节的文件。测试结果:

sum:220152087 time:1464
sum:220152087 time:72
sum:220152087 time:25

 

说明读数据无误。删去其中的数据处理部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import  java.io.BufferedInputStream;
import  java.io.FileInputStream;
import  java.io.FileNotFoundException;
import  java.io.IOException;
import  java.io.RandomAccessFile;
import  java.nio.MappedByteBuffer;
import  java.nio.channels.FileChannel;
 
public  class  Test {
 
 
public  static  void  main(String[] args) {
try  {
FileInputStream fis= new  FileInputStream( "/home/tobacco/test/res.txt" );
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
try  {
while ((n=fis.read())>= 0 ){
//sum+=n;
}
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
try  {
FileInputStream fis= new  FileInputStream( "/home/tobacco/test/res.txt" );
BufferedInputStream bis= new  BufferedInputStream(fis);
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
try  {
while ((n=bis.read())>= 0 ){
//sum+=n;
}
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
MappedByteBuffer buffer= null ;
try  {
buffer= new  RandomAccessFile( "/home/tobacco/test/res.txt" , "rw" ).getChannel().map(FileChannel.MapMode.READ_WRITE,  0 1253244 );
int  sum= 0 ;
int  n;
long  t1=System.currentTimeMillis();
for ( int  i= 0 ;i< 1253244 ;i++){
//n=0x000000ff&buffer.get(i);
//sum+=n;
}
long  t=System.currentTimeMillis()-t1;
System.out.println( "sum:" +sum+ " time:" +t);
catch  (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
catch  (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
 
}
 
}

  

测试结果:

sum:0 time:1458
sum:0 time:67
sum:0 time:8

由此可见,将文件部分或者全部映射到内存后进行读写,速度将提高很多。
这是因为内存映射文件首先将外存上的文件映射到内存中的一块连续区域,被当成一个字节数组进行处理,读写操作直接对内存进行操作,而后再将内存区域重新映射到外存文件,这就节省了中间频繁的对外存进行读写的时间,大大降低了读写时间。


目录
相关文章
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
21天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
1月前
|
缓存 easyexcel Java
Java EasyExcel 导出报内存溢出如何解决
大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!
164 1
|
2天前
|
Java
java内存区域
1)栈内存:保存所有的对象名称 2)堆内存:保存每个对象的具体属性 3)全局数据区:保存static类型的属性 4)全局代码区:保存所有的方法定义
8 1
|
16天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
14天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
36 2
|
20天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
35 2
|
23天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
21天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
49 1
|
24天前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
34 4
下一篇
无影云桌面