当前位置:C++技术网 > 精选 > C++文件加密解密功能的实现过程详细分析:3 内存映射文件技术加速大文件的处理

C++文件加密解密功能的实现过程详细分析:3 内存映射文件技术加速大文件的处理

更新时间:2016-06-20 23:08:42浏览次数:1+次

    如果说你只处理几十MB最多也就几百MB的文件,传统文件读写还是可以的。然而,当你要处理GB-TB这么大的文件的时候,传统的文件读写就太鸡肋了。首先是一次性不能处理太多的数据,否则内存爆满。如果一次太少了,那么大的文件,不知道什么时候处理的完。是不是有一个更好的技术来满足这样的需求呢?确实有,那就是内存映射文件技术。
    实际上,系统的虚拟内存也就用了内存映射文件技术,用来扩充内存容量的。这个我就不多讲,有兴趣自己看书,可以参考《Windows核心编程》。当然,我们也可以将它用来处理普通的大文件咯。

    我们先来看一张图:

传统文件读写文件和内存映射文件技术读写文件

【传统文件读写文件和内存映射文件技术读写文件】
    这张图简单示意了传统文件读写和内存映射文件技术实现的文件读写的区别。其实你看到的区别也就是内存中多了一个缓冲区的区别。也就是这个基本的区别,引申出了很多不一样的地方。
    传统的文件操作,总是将文件从磁盘如硬盘加载到内存,也就是我们看到的缓冲区,代码中也就是一个数组之类的。然后操作缓冲区的数据,操作完后,再写回到硬盘。这个模式在我们前面两篇文章已经讲过了。如果你对此过程不清楚,请阅读《C++文件加密解密功能的实现过程详细分析1:打开文件和读取文件》和《C++文件加密解密功能的实现过程详细分析2:加密处理和数据写回》。
    对于1.2GB的视频文件的简单加密,时间在22秒左右。感觉速度还行,但是有很大的提升空间。从整个过程来看,因为有大量的IO操作即文件读写,所以速度也就被拖慢了。而且这个IO操作是高层次的文件读写操作,所以性能也是不够的,系统不好优化。本身也因为需要缓冲区的支持,增加了内存的巨大开销,如果读写文件出错,还要重试。如果是超大文件,处理起来太费力了,时间也很长。
    所以要改进这个程序,就需要使用内存映射文件技术了。简单的来说,内存映射文件技术就是直接将文件当做缓冲区了。是不是使用了内存映射文件就不需要将硬盘的数据读入到内存处理了呢?不是的。文件要被处理,始终是要到内存中的。不过不需要程序员去读写了。
    也就是说,实际的文件读写过程被隐藏在系统内部了,这有一个好处,也就是编码变得简单了。内存映射文件,将文件当做缓冲区,我们操作文件就和操作内存一样,就和读写数组一样的。是不是简单的多了。这样文件的实际的读写则完全交给系统,系统可以采用各种优化算法进行优化,比如预读取、缓存技术(不是我们用的缓冲区)、异步读写、流水线技术等等。这样系统内部优化后,降低了文件读写的次数,而且提前缓存了数据,也可以使用对齐,保证每次读写的数据都是整齐的,这样都可以提高性能。而这一切,对于我们程序员来说,都是看不到的。我们只能看到,速度变快了,代码变简洁了。
     和传统文件读写一对比,立马就看到了内存映射文件技术的好处,那么我将用内存映射文件技术来改进加密解密文件功能的程序,完整代码如下:
#include <Windows.h>
void main()
{
    HANDLE hFile = CreateFile(L"D:/科洛弗道10号.rmvb", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE ,NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE)return;
    DWORD MapSize = 1024*1024*256;//映射的大小,相当于缓冲区大小
    LARGE_INTEGER FileSize,HandleFileSize,LeftFileSize;
    HandleFileSize.QuadPart=0;
    if(!GetFileSizeEx(hFile,&FileSize))
    {
        MessageBox(NULL,L"获取文件大小出错",L"出错",0);
        return;
    }
    LeftFileSize.QuadPart = FileSize.QuadPart;

    //内存映射文件技术,不用缓冲,不用读写文件,没有读写重试,6-13S,传统的读写20-24S
    HANDLE hFileMap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,FileSize.HighPart,FileSize.LowPart,NULL);
    char* pAddr =0;//映射起始地址
    while(HandleFileSize.QuadPart<FileSize.QuadPart)
    {
        if (LeftFileSize.QuadPart<MapSize)MapSize = LeftFileSize.QuadPart;
        //映射一段数据,返回这段数据的起始地址
        pAddr = (char*)MapViewOfFile(hFileMap,FILE_MAP_WRITE,HandleFileSize.HighPart,HandleFileSize.LowPart,MapSize);
        for (int i=0;i<MapSize;i++)
        {
            //像操作内存数组一样操作文件数据
            (*(pAddr+i))++;//加密处理
        }
        HandleFileSize.QuadPart+=MapSize;
        LeftFileSize.QuadPart-=MapSize;
        //FlushViewOfFile(pAddr,MapSize);//不必时刻刷,让系统自己慢慢的写回去,提高性能
        UnmapViewOfFile(pAddr);//解映射,这样可以映射到下一段
    }
    //关闭句柄
    CloseHandle(hFile);
    CloseHandle(hFileMap);
}

    你可以明显看到,代码瘦了一大圈,速度还快了不少,这可是多少程序员追求的目标呀。前面打开文件获取文件大小部分和之前的代码一样,后面开始就完全不一样了。而内存映射文件技术的使用,也是很简单的。至于文件分多次读写之类的,和之前的都是一样的。只是这里去掉了重试、去掉了文件读写,去掉了缓冲,取而代之的是创建一个内存映射,然后分段映射,然后就是直接像内存操作一样操作文件数据了。最后就是解映射。循环的分段映射和解映射,循环过程中不断的推进文件的操作,和前面文件读写是一个模式,只是这里换成了映射而已。就好像你只需要指定本次要开始处理的数据的位置和处理的大小,然后就可以操作了。
    这一段操作代码,基本上满足你日后的大部分内存映射文件的使用需求。如果你对内存映射文件的几个API函数感觉到陌生,多看看就好了。有了前面两篇文章的分析,再看这里使用的内存映射文件技术实现,太简单了。这样循序渐进,不仅可以让你知道为什么要用内存映射文件技术,而且让你从熟悉的技术对比的进入不熟悉的技术,而且模式还是一样的,还变得简单了。
    那么这几个API函数CreateFileMapping、MapViewOfFile、FlushViewOfFile、UnmapViewOfFile本身的参数介绍,就不在此讲述了。从参数来看,你也能知一二了。没有什么好讲的。如果要铺开,又有很多。所以,你可以自己查阅一下MSDN深入了解各个参数的意义。