使用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

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


目录
相关文章
|
2月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
217 3
|
3月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
25天前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
44 4
|
29天前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
1月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
135 1
|
2月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
440 17
|
3月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
170 1
|
3月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。
|
4月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
222 0