处理大文件之内存映射

内存映射处理大文件

  • 运行环境: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的文件权限系统)。

  1. PROT_EXEC //页内容可以被执行。
  2. PROT_READ //页内容可以被读取。
  3. PROT_WRITE //页可以被写入。
  4. **PROT_NONE ** //页不可访问。

flags:指定映射对象的类型,映射选项和映射页是否可以共享。

  1. MAP_FIXED //使用指定的映射起始地址,如果由start和length参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
  2. MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
  3. MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
  4. MAP_DENYWRITE //这个标志被忽略。
  5. MAP_EXECUTABLE //同上。
  6. MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
  7. MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
  8. MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
  9. MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
  10. MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
  11. MAP_FILE //兼容标志,被忽略。
  12. MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
  13. MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
  14. MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。

off_toffset:被映射对象内容的偏移量。

posted @ 2017-08-08 21:50  chaffee  阅读(1573)  评论(0编辑  收藏  举报