【WIN32进阶之路】:内存映射文件
遇到一个问题,如果一个客户数据文件有2g大,客户要通过界面查询文件中的数据并用列表控件显示数据,要怎么处理这个文件才能让应用程序不会长时间无响应,客户感觉不到程序的卡顿?
第二章:解决之道
第一反应是用内存映射文件处理大文件,将文件分成数次映射至内存地址空间,避免一次性缓冲整个文件带来的界面长时间无响应,并将暂时不用的内存映射文件取消映射,客户是用列表控件进行显示,那就可以在首次加载的时候只加载第一页的内容就进行显示,之后在缓冲下一页的内容,当用户查看中间页面的内容时可以缓冲将当前页面的上下页面和首页、最后页面对应的文件加载至进程地址空间中,其他的文件映射视图取消映射。
进一步完善创建多线程处理,主线程创建文件映射内核对象,多个子线程分别映射磁盘文件的一部分至进程地址空间并进行数据分析,处理完数据的线程通过SendMessage或者PostMessage又或者锁、关键段、内核线程同步方式等通知界面线程,而界面线程再做一些NOT WAIT操作,添加两个进度条提示首页加载进度和整体文件加载进度,首页加载完成时隐藏进度条并显示数据。
第三章:正文
3A:内存映射文件介绍:
创建内存映射文件相当于先调拨一块地址空间区域,然后给区域调拨物理存储器。不同之处在于内存映射文件的物理存储器来自于磁盘上的文件,而不是从系统的页交换文件中分配的。
3B:内存映射文件主要用于以下三种情况:
1.系统使用内存映射文件来载入并运行.exe和动态链接库(.dll)文件,可以节省页交换文件的空间和应用程序启动的时间。
2.开发人员可以用内存映射文件来访问磁盘上的数据文件,使得我们可以避免直接对文件进行IO操作和对文件内容进行缓存。
3.通过内存映射文件,我们可以在同一台机器的不同进程之间共享数据,windows也提供了其他一些方式来在进程间传送数据,但这些方法都是通过内存映射文件包装而成,因此最快的同一台机器上的进程间共享数据方法就是内存映射文件。
3C:常用API
CreateFile:打开文件内核对象
CreateFileMapping:创建一个指定名称的文件映射内核对象,通过参数指定映射到进程地址空间的文件句柄。
OpenFileMapping:打开一个指定名称的文件映射内核对象。
MapViewOfFile:把文件数据映射到进程地址空间,通过参数指定映射到进程地址空间的文件映射句柄和文件访问方式。
FlushViewOfFile:处于速度上的考虑,系统会对文件数据的页面进行缓存处理,这样在处理文件映射视图时就不需要随时更新磁盘上的文件,调用FlushViewOfFile将文件映象的修改立即从高速缓存写入磁盘映象。
对内存映射文件的处理类似读取到内存中的文件,或者理解成操作一串字符串。
UnMapViewOfFile:完成文件数据映象的释放。
3D:注意事项:
可以对同一个文件创建多个内存映射文件,但系统并不保证多个内存映射文件之间的数据一致性,系统只保证同一个内存映射文件的多个视图数据一致。
用作内存映射文件的磁盘文件最好是只读属性,可以避免其他进程对文件修改二造成的映射文件和磁盘文件的不一致,如果无法确认文件是只读的,那么在创建文件对象的CreateFile时指定访问权限为独占。
不应该用内存映射文件来跨网络共享可写文件,系统无法知晓网络上另外一台电脑对文件的操作而继续使用内存中的原始数据。
内存映射文件不一定需要磁盘文件,也可以创建以页交换文件为后备存储器的内存映射文件。与创建以磁盘文件为物理存储器的内存映射文件不同的步骤是:不需CreateFile步骤,在CreateFileMapping时文件内核对象传参INVALID_HANDLE_VALUE,这就告诉系统物理存储设备从页交换文件分配而不是磁盘文件。
第四章:补充话题:
4A:稀疏调拨的内存映射文件
之前的内存映射文件要么从磁盘上的数据文件中调拨,要么从页交换文件中调拨,这意味着对存储器的使用可能并不如我们希望的那么高效。按照jeffrey大神的例子,有一个电子表格文件定义:
CELLDATA CellData[200][256];如果CELLDATA的大小是128字节,那么这个数组就需要6553600字节的物理存储器,这也就表示程序开始运行就需要从页交换文件中分配大量的物理存储器,但是用户通常只用前面几个单元格存放信息,绝大多数的单元格都是浪费的。
我们希望把电子表格作为文件映射对象来共享,但又不想一开始就分配这么大的物理存储器,但是采用文件分片映射视图的方式编程会很繁琐,此时我们可以采用稀疏文件映射方式。
稀疏调拨的内存映射文件方法:
CreateFileMapping的fdwProtect参数中指定SEC_RESERVE或SEC_COMMIT,这两个标记只有在以页交换文件为后备存储器来创建文件映射对象时才有意义,SEC_COMMIT标志让CreateFileMapping从页交换文件中调拨存储器。
如果在调用CreateFileMapping的时候传入SEC_RESERVE标志,那么系统不会从页交换文件中调拨屋里存储器,它只返回文件映射对象的句柄。
MapViewOfFile给指定的文件映射对象创建一个视图,即预定一块地址空间区域,但不会给该区域调拨任何物理存储器,此时访问该区域的操作都是非法的。
调用VirtualAlloc给共享区域调拨物理存储器,我们可以只调拨部分存储器给MapViewOfFile预定的地址空间区域,调拨物理存储器后所有映射了同一个文件映射对象的的视图的其他进程就可以成功的访问已调拨的页面。
PS:稀疏调拨的内存映射文件尚未实例验证过,诸君谨慎参考。