处理大文件之内存映射

内存映射处理大文件

  • 运行环境: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:被映射对象内容的偏移量。

转载于:https://www.cnblogs.com/chanfee/p/7148159.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于大文件的读写操作,Qt提供了多种方法,其中一种常用的方法是使用内存映射Memory Mapping)技术。 内存映射允许将文件的内容映射到进程的地址空间中,从而可以直接通过内存访问文件的内容,而不需要显式地进行读写操作。这种方式可以提高读写效率,并且对于大文件处理更为高效。 在Qt中,可以使用QFile和QIODevice类提供的内存映射功能来实现大文件的读写操作。具体步骤如下: 1. 打开文件:使用QFile类打开要进行内存映射文件。 ```cpp QFile file("path/to/file"); if (file.open(QIODevice::ReadWrite)) { // 文件打开成功 } ``` 2. 创建内存映射:使用QFile的map()函数创建一个QMemoryMappedFile对象,并将其指定为读写模式或只读模式。 ```cpp QMemoryMappedFile memFile; if (memFile.open()) { if (memFile.map(&file, QMemoryMappedFile::ReadWrite)) { // 内存映射创建成功 } } ``` 3. 获取映射的数据指针:通过QMemoryMappedFile对象的data()函数获取映射的数据指针。 ```cpp char* data = static_cast<char*>(memFile.data()); ``` 4. 进行读写操作:可以直接通过data指针对文件内容进行读写操作,类似于普通的内存操作。 ```cpp // 写入数据 memcpy(data, "Hello, world!", 14); // 读取数据 QString content = QString::fromUtf8(data); ``` 5. 解除内存映射和关闭文件:完成读写操作后,需要解除内存映射并关闭文件。 ```cpp memFile.unmap(); file.close(); ``` 需要注意的是,使用内存映射技术需要谨慎处理文件大小和内存占用,避免因为大文件或者内存限制而导致程序崩溃或性能问题。此外,还需要注意处理文件不存在或者无法打开的异常情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值