内存映射处理大文件
- 运行环境:linux
- 实现语言:C++
- 文件大小:大于10G
1、为什么要用内存映射
a、一般读写大文件操作会带来较多的磁盘IO开销
b、数据流一次性写入大量数据到内存容易达到内存限制 c、效率问题
2、基本概念
2.1 内存映射
简单定义:
一个文件到一块内存的映射。
解释:
1、物理内存(Physical memory):相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间。
2、虚拟内存(Virtual memory):虚拟内存则是指将硬盘的一块区域划分来作为内存。其主要作用是在计算机运行时为操作系统和各种程序提供临时储存。3、内存映射(Memory map):与虚拟内存类似。利用进程中与磁盘上的文件大小相同的逻辑内存进行映射,并进行寻址访问,其过程就如同对加载了文件的内存空间进行访问。
3、方案
通过nmap(一种系统调用方法)将磁盘文件映射进内存。
3.1 实例:利用内存映射对爬虫采集的html页面数据进行过滤处理
#include#include #include #include #include #include "header/commonForAll.h"#include "header/splitHtmlFromNutch.h"#include "header/memoryMapping.h"#include "header/cParseIniFile.h"using namespace std;int htmlSplitNum = 0;int viewMapSize = 0;char* htmloutput;char* outputFileCommon; //输出文件公共文件名部分string::size_type STR_FIND_RETURN; //查找字符串返回值string DOCTYPE = ""; //待查找起始字符串string _HTML = ""; //待查找结束字符串bool IS_WRITING = false;int main(int argc, char **argv) { /********加入时间测试********/ struct timeval start; struct timeval end; unsigned long timer; gettimeofday(&start,NULL); /***********************读取配置文件**********************/ /**************************开始************************/ CParseIniFile parseIniFile; const string configPath = "/home/chaffee/work/projects/semantic_library/cpp/SplitHtmlFromNutch/splitHtmlFromNutch.ini"; //判断配置文件是否存在 if(!CommonForAll::isExistFile((char*)configPath.c_str())){ cout << "该文件splitHtmlFromNutch.ini不存在" << endl; return 0; } std::map configContent; std::map ::iterator iter; const char* configSection = "main_config"; parseIniFile.ReadConfig(configPath, configContent, configSection); /**************************结束************************/ htmloutput = (char*)configContent["htmloutput"].c_str(); outputFileCommon = (char*)configContent["outputfilecommon"].c_str(); htmlSplitNum = atoi(configContent["htmlsplitnum"].c_str()); viewMapSize = atoi(configContent["viewmapsize"].c_str()); SplitHtmlFromNutch shfromNuntch(configContent["htmlinput"].c_str(), htmlSplitNum); if (!shfromNuntch.ISEXIST_INPUTFILE()) { cout << "输入文件不存在" << endl; return 0; } //判断输出目录是否存在 if(!CommonForAll::isExistDir(htmloutput)){ cout << "输出目录不存在" << endl; return 0; } MemoryMapping memoryMap; //设置内存映射分页大小 memoryMap.setViewMapSize(viewMapSize); //获取有效的文件描述 int fd = CommonForAll::getFd((char*)shfromNuntch.HTMLPATH); if(fd < 0){ cout << "输入文件错误" << endl; return 0; } //获取文件大小 long fileLen = CommonForAll::getFileSize((char*)shfromNuntch.HTMLPATH); if(fileLen <= 0){ cout << "输入文件大小为0" << endl; return 0; } long count = 0; //分页计数 long int offset = 0; //分页偏移量 ofstream ofstrFile; int outFileCount = 0; int htmlCount = 0; /*********输出文件路径拼接**********/ char htmlOutputDir[512]; //兼容输出路径是否带'/' if(!(htmloutput[strlen(htmloutput) - 1] == '/')){ sprintf(htmloutput, "%s%c", htmloutput, '/'); } snprintf(htmlOutputDir, sizeof(htmlOutputDir), "%s%s%d%s", htmloutput, outputFileCommon, outFileCount, ".html"); ofstrFile.open(htmlOutputDir, ios::out); //====================开始分页映射操作======================= while((fileLen - count * memoryMap.VIEWMAPSIZE) > 0){ memoryMap.doPaging(memoryMap.VIEWMAPSIZE, fd, offset); const char* mmapBuf = memoryMap.memMap; const char* mmapStart = memoryMap.memMap; int len = 0; while(mmapStart != NULL){ mmapStart = CommonForAll::_get_line(mmapBuf,&len); string strLine(mmapBuf,len); if(!strLine.empty()){ //按照一百个html页面进行分割 //------------------start------------------ if (100 == htmlCount) { outFileCount++; //用snprintf代替sprintf,标明大小sizeof(htmlOutputDir),防止核心内存操作错误 snprintf(htmlOutputDir, sizeof(htmlOutputDir), "%s%s%d%s", htmloutput, outputFileCommon, outFileCount, ".html"); ofstrFile.flush(); ofstrFile.clear(); ofstrFile.close(); ofstrFile.open(htmlOutputDir); htmlCount = 0; } if (IS_WRITING) { STR_FIND_RETURN = strLine.find(_HTML); if (STR_FIND_RETURN != string::npos) { if (ofstrFile.is_open()) { ofstrFile << strLine << endl; } IS_WRITING = false; htmlCount++; } else { if (ofstrFile.is_open()) { ofstrFile << strLine << endl; } } } else { STR_FIND_RETURN = strLine.find(DOCTYPE); if (STR_FIND_RETURN != string::npos) { IS_WRITING = true; if (ofstrFile.is_open()) { ofstrFile << strLine << endl; } } } } //------------------end------------------ mmapBuf = mmapStart; } offset += memoryMap.VIEWMAPSIZE; count++; munmap(memoryMap.memMap, memoryMap.VIEWMAPSIZE); msync(memoryMap.memMap, memoryMap.VIEWMAPSIZE, MS_SYNC); memoryMap.memMap = NULL; } if(ofstrFile){ ofstrFile.flush(); ofstrFile.clear(); ofstrFile.close(); } close(fd); /********打印测试时间**********/ gettimeofday(&end,NULL); timer = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec; printf("程序用时 = %ld us\n",timer); return 0;}
核心映射类:
#include "header/memoryMapping.h"using namespace std;MemoryMapping::MemoryMapping() : memMap(NULL) , VIEWMAPSIZE(0){}MemoryMapping::~MemoryMapping() { if (this->memMap) { munmap(this->memMap, this->VIEWMAPSIZE); msync(this->memMap, this->VIEWMAPSIZE, MS_SYNC); } //cout << "内存映射析构方法" << endl;}void MemoryMapping::setViewMapSize(size_t length) { this->VIEWMAPSIZE = length;}bool MemoryMapping::doPaging(size_t length, int fd, off_t offset) { this->memMap = (char*)mmap(NULL, length, PROT_READ, MAP_SHARED, fd, offset); if(this->memMap != NULL) { return true; }else{ return false; }}
映射函数及参数解释(引自百度百科):
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);start:映射区的开始地址,设置为0或者null时表示由系统决定映射区的起始地址。
length:映射区的长度。长度单位是以字节(KB)为单位,不足一个内存页按一个内存页处理。prot:期望的内存保护标志,不能与文件的打开模式冲突。通过下列的单个或者利用or运算合理地组合(类似于linux的文件权限系统)。
- PROT_EXEC //页内容可以被执行。
- PROT_READ //页内容可以被读取。
- PROT_WRITE //页可以被写入。
- PROT_NONE //页不可访问。
flags:指定映射对象的类型,映射选项和映射页是否可以共享。
- MAP_FIXED //使用指定的映射起始地址,如果由start和length参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
- MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
- MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
- MAP_DENYWRITE //这个标志被忽略。
- MAP_EXECUTABLE //同上。
- MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
- MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
- MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
- MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
- MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
- MAP_FILE //兼容标志,被忽略。
- MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
- MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
- MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
off_toffset:被映射对象内容的偏移量。