内存映射处理大文件
- 运行环境: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 <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <sys/time.h>
#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 = "<!DOCTYPE html>"; //待查找起始字符串
string _HTML = "</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<string, string> configContent;
std::map<string, string>::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:被映射对象内容的偏移量。