一、为什么需要内存映射 “内存映射文件”
可以将硬盘上的文件映射到虚拟地址空间
,这样就不需要将所有东西都放入到页交换文件中,比如系统有许多程序同时运行时,如果将这些程序文件都加载到页交换文件中,页交换文件
将会变得非常大。事实上,Windows 也并没有将硬盘上的程序文件复制到页交换文件
中,因为这样不仅会让页交换文件
将会变得非常大,也会浪费很多时间,特别是可执行程序非常大的时候。
当用户要求执行一个应用程序时,系统会打开该应用程序的.exe
文件,并计算出应用程序的代码和数据的大小,然后系统会在进程的虚拟地址空间预定一块地址空间,并注明与该区域相关联的物理存储器就是.exe
文件本身。
当把一个位于硬盘上的文件(可以是.exe
,.dll
也可以是普通文件)映像用作地址空间区域对应的物理存储器时,我们称这个文件映像为“内存映射文件”
。
现在我们可以对Windows内存体系(2)--虚拟内存 第 2 节的图进行完善了,加入“内存映射文件”部分:
二、内存映射文件技术介绍 常用的有 Win32 API 的CreateFile()
、WriteFile()
、ReadFile()
和 MFC 提供的CFile
类都可以实现文件的读写操作。一般来说,以上这些函数可以满足大多数场合的要求,但是对于某些特殊应用领域所需要的动辄几十 GB、几百 GB、乃至几 TB 的海量存储,此时在以平常的文件处理方法进行处理显然是行不通的(效率低下,而且内存没那么大)。目前,对于这种大文件的操作一般是以内存映射文件的方式来加以处理的。
内存映射文件也是 Windows 的一种内存管理方法,提供了一个统一的内存管理特征,使应用程序可以通过内存指针对磁盘上的文件进行访问。通过文件映射将磁盘文件内容(全部或者部分)与进程虚拟地址空间的某个区域建立映射关联,可以直接对被映射的文件进行访问,而不必执行文件 I/O 操作也无需对文件内容进行缓冲处理。内存文件映射的这种特性是非常适合于用来管理大尺寸文件的。
三、大文件读写实例 通过 C++调用系统 API 实现文件映射的步骤大致如下:
本示例首先在D:\
生成一个大小为 1GB 的BigFile.data
文件,然后使用内存映射技术将该文件内全部填充字符 A,随后读取其中的第20000~20100字节
,并将这些字节修改为字符 B,然后再次读取已验证是否修改成功。
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 77 78 79 80 81 82 #include <windows.h> void Test () { HANDLE file_ = CreateFile (TEXT ("D:\\BigFile.data" ), GENERIC_READ | GENERIC_WRITE, 0 , NULL , OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if (file_ == INVALID_HANDLE_VALUE) { printf ("CreateFile failed, GLE:%d\n" , GetLastError ()); return ; } LARGE_INTEGER filesize; filesize.QuadPart = 1024 * 1024 * 1024 ; HANDLE mapping_ = CreateFileMapping (file_, NULL , PAGE_READWRITE, filesize.HighPart, filesize.LowPart, NULL ); if (mapping_ == NULL ) { printf ("CreateFileMapping failed, GLE:%d\n" , GetLastError ()); return ; } LARGE_INTEGER offset; offset.QuadPart = 0 ; LPVOID mapping_addr = MapViewOfFile (mapping_, FILE_MAP_WRITE | FILE_MAP_READ, offset.HighPart, offset.LowPart, 0 ); if (mapping_addr == NULL ) { printf ("MapViewOfFile failed, GLE:%d\n" , GetLastError ()); return ; } char buf[1024 ]; for (int i = 0 ; i < 1024 ; i++) { buf[i] = 'A' ; } for (long l = 0 ; l < 1024 * 1024 ; l++) { memcpy ((LPVOID)((long )mapping_addr + l * 1024 ), buf, 1024 ); } char read_content[101 ] = { 0 }; memcpy (read_content, (LPVOID)((long )mapping_addr + 20000 ), 100 ); printf ("%s\n" , read_content); char write_content[100 ]; for (int i = 0 ; i < 100 ; i++) { write_content[i] = 'B' ; } memcpy ((LPVOID)((long )mapping_addr + 20000 ), write_content, 100 ); memcpy (read_content, (LPVOID)((long )mapping_addr + 20000 ), 100 ); printf ("%s\n" , read_content); UnmapViewOfFile (mapping_addr); CloseHandle (mapping_); CloseHandle (file_); return ; } int main () { Test (); return 0 ; }
文章图片带有“CSDN”水印的说明: 由于该文章和图片最初发表在我的CSDN 博客 中,因此图片被 CSDN 自动添加了水印。