如何解析不能完全适合内存RAM的文件

时间:2016-12-21 09:34:03

标签: c performance file parsing memory-management

我已经创建了一个框架来解析合适大小的文本文件,这些文件可以放在内存RAM中,而且现在情况还顺利。我没有抱怨,但如果遇到我必须处理大文件的情况,比如大于8GB(这是我的大小)怎么办? 什么是处理这些大文件的有效方法?

我的框架:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int Parse(const char *filename,
    const char *outputfile);

int main(void)
{
    clock_t t1 = clock();
    /* ............................................................................................................................. */
    Parse("file.txt", NULL);
    /* ............................................................................................................................. */
    clock_t t2 = clock();
    fprintf(stderr, "time elapsed: %.4f\n", (double)(t2 - t1) / CLOCKS_PER_SEC);
    fprintf(stderr, "Press any key to continue . . . ");
    getchar();
    return 0;
}

long GetFileSize(FILE * fp)
{
    long f_size;
    fseek(fp, 0L, SEEK_END);
    f_size = ftell(fp);
    fseek(fp, 0L, SEEK_SET);
    return f_size;
}

char *dump_file_to_array(FILE *fp,
    size_t f_size)
{
    char *buf = (char *)calloc(f_size + 1, 1);
    if (buf) {
        size_t n = 0;
        while (fgets(buf + n, INT_MAX, fp)) {
            n += strlen(buf + n);
        }
    }
    return buf;
}

int Parse(const char *filename,
    const char *outputfile)
{
    /* open file for reading in text mode */
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        perror(filename);
        return 1;
    }
    /* store file in dynamic memory and close file */
    size_t f_size = GetFileSize(fp);
    char *buf = dump_file_to_array(fp, f_size);
    fclose(fp);
    if (!buf) {
        fputs("error: memory allocation failed.\n", stderr);
        return 2;
    }
    /* state machine variables */
    // ........

    /* array index variables */
    size_t x = 0;
    size_t y = 0;
    /* main loop */
    while (buf[x]) {
        switch (buf[x]) {
            /* ... */
        }
        x++;
    }
    /* NUL-terminate array at y */
    buf[y] = '\0';
    /* write buffer to file and clean up */
    outputfile ? fp = fopen(outputfile, "w") :
                 fp = fopen(filename, "w");
    if (!fp) {
        outputfile ? perror(outputfile) :
                     perror(filename);
    }
    else {
        fputs(buf, fp);
        fclose(fp);
    }
    free(buf);
    return 0;
}

基于框架的模式删除功能:

int delete_pattern_in_file(const char *filename,
    const char *pattern, const char *outputfile)
{
    /* open file for reading in text mode */
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        perror(filename);
        return 1;
    }
    /* copy file contents to buffer and close file */
    size_t f_size = GetFileSize(fp);
    char *buf = dump_file_to_array(fp, f_size);
    fclose(fp);
    if (!buf) {
        fputs("error - memory allocation failed", stderr);
        return 2;
    }
    /* delete first match */
    size_t n = 0, pattern_len = strlen(pattern);
    char *tmp, *ptr = strstr(buf, pattern);
    if (!ptr) {
        fputs("No match found.\n", stderr);
        free(buf);
        return -1;
    }
    else {
        n = ptr - buf;
        ptr += pattern_len;
        tmp = ptr;
    }
    /* delete the rest */
    while (ptr = strstr(ptr, pattern)) {
        while (tmp < ptr) {
            buf[n++] = *tmp++;
        }
        ptr += pattern_len;
        tmp = ptr;
    }
    /* copy the rest of the buffer */
    strcpy(buf + n, tmp);
    /* open file for writing and print the processed buffer to it */
    outputfile ? fp = fopen(outputfile, "w") :
                 fp = fopen(filename, "w");
    if (!fp) {
        outputfile ? perror(outputfile) :
                     perror(filename);
    }
    else {
        fputs(buf, fp);
        fclose(fp);
    }
    free(buf);
    return 0;
}

8 个答案:

答案 0 :(得分:11)

如果您希望坚持使用当前的设计,可以选择mmap()文件,而不是将其读入内存缓冲区。

您可以将函数dump_file_to_array更改为以下(特定于Linux):

char *dump_file_to_array(FILE *fp, size_t f_size) {
   buf = mmap(NULL, f_size, PROT_READ, MAP_SHARED, fileno(fp), 0);
   if (buf == MAP_FAILED)
       return NULL;
   return buf;
}

现在你可以读取文件了,内存管理器会自动关注只保存文件内存中的相关部分。 对于Windows,存在类似的机制。

答案 1 :(得分:2)

您可能会逐行解析文件。所以读一个大块(4k或16k)并解析其中的所有行。将小余数复制到4k或16k缓冲区的开头,然后读入缓冲区的其余部分。冲洗并重复。

对于JSON或XML,您需要一个可以接受多个块或输入的基于事件的解析器。

答案 2 :(得分:2)

您的方法存在多个问题。

最大可用内存的概念并不那么明显:从技术上讲,您不受RAM大小的限制,而是受环境允许的内存量的限制你分配和使用你的程序。这取决于各种因素:

  • 您编译的ABI:如果编译32位代码,程序可访问的最大内存大小限制为小于4 GB,即使您的系统RAM多于此值。
  • 系统配置的配额允许您的程序使用。这可能比可用内存少。
  • 当请求更多内存而不是物理上可用时,系统使用什么策略:大多数现代系统使用虚拟内存并在进程和系统任务(例如磁盘缓存)之间共享物理内存,使用非常高级的算法,这些算法无法在几行。在某些系统上,您的程序可以分配和使用比在主板上物理安装更多的内存,在访问更多内存时将内存页交换到磁盘,延迟时间成本很高。

您的代码还有其他问题:

  • 类型long可能太小而无法容纳文件大小:在Windows系统上,{64}版本上的long是32位甚至可以分配大于2GB的块。您必须使用不同的API从系统请求文件大小。
  • 您通过对fgets()的一系列调用来阅读该文件。这是低效的,单次调用fread()就足够了。此外,如果文件包含嵌入的空字节('\ 0'字符),则文件中的块将在内存中丢失。但是,如果使用strstr()strcpy()等字符串函数来处理字符串删除任务,则无法处理嵌入的空字节。
  • while (ptr = strstr(ptr, pattern))中的条件是作业。虽然不是严格错误,但它的风格很差,因为它会使代码的读者感到困惑,并且在编译错误的情况下编译器会阻止生命保护警告。你可能认为这种情况永远不会发生,但是任何人都可以在测试中输入错字并且缺少=很难发现,并且会产生可怕的后果。
  • 您使用三元运算符代替if语句也非常混乱:outputfile ? fp = fopen(outputfile, "w") : fp = fopen(filename, "w");
  • 重写输入文件也存在风险:如果出现任何问题,输入文件将丢失。

请注意,您可以动态实施过滤,无需缓冲,尽管效率低下:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "usage: delpat PATTERN < inputfile > outputfile\n");
        return 1;
    }
    unsigned char *pattern = (unsigned char*)argv[1];
    size_t i, j, n = strlen(argv[1]);
    size_t skip[n + 1];
    int c;

    skip[0] = 0;
    for (i = j = 1; i < n; i++) {
        while (memcmp(pattern, pattern + j, i - j)) {
            j++;
        }
        skip[i] = j;
    }

    i = 0;
    while ((c = getchar()) != EOF) {
        for (;;) {
            if (i < n && c == pattern[i]) {
                if (++i == n) {
                    i = 0; /* match found, consumed */
                }
                break;
            }
            if (i == 0) {
                putchar(c);
                break;
            }
            for (j = 0; j < skip[i]; j++) {
                putchar(pattern[j]);
            }
            i -= skip[i];
        }
    }
    for (j = 0; j < i; j++) {
        putchar(pattern[j]);
    }
    return 0;
}

答案 3 :(得分:1)

首先,我不建议在RAM中保存这么大的文件,而是使用流。这是因为缓冲通常由库以及内核完成。

如果您按顺序访问文件(这似乎是这种情况),那么您可能知道所有现代系统都实现了预读算法,因此只需提前读取整个文件IN RAM在大多数情况下可能只是浪费时间。

您没有指定必须覆盖的用例,所以我将不得不假设使用像

这样的流
std::ifstream

并且即时解析将满足您的需求。另外,还要确保您对预期较大的文件的操作是在单独的线程中完成的。

答案 4 :(得分:0)

另一种解决方案:如果您使用的是Linux系统,并且拥有相当数量的交换空间,那么就打开整个坏孩子吧。它会消耗你的内存并消耗硬盘空间(交换)。因此,您可以立即打开整个物品,但不是所有物品都在撞锤上。

赞成

  • 如果发生意外关闭,则交换空间上的内存可以恢复。
  • RAM价格昂贵,硬盘驱动器便宜,因此应用程序可以减轻您昂贵设备的负担
  • 病毒无法损害您的计算机,因为RAM无法运行
  • 您将通过使用交换空间充分利用Linux操作系统。通常情况下,交换空间模块不会被使用,它所做的就是堵塞了宝石。
  • 利用整个撞锤所需的额外能量可以使直接区域变暖。在冬季有用
  • 您可以在简历中添加“复杂和特殊内存分配工程”。

缺点

答案 5 :(得分:0)

考虑将文件视为行的外部数组

代码可以使用行索引数组。该索引数组可以以大文件大小的一小部分保存在内存中。通过此查找可快速访问任何行,fsetpos()fread()/fgets()。在编辑行时,新行可以按任何顺序保存在临时文本文件中。保存文件会依次读取原始文件和临时文件,以形成和写入新文件。

typedef struct {
  int attributes; // not_yet_read, line_offset/length_determined, 
                  // line_changed/in_other_file, deleted, etc.
  fpos_t line_offset;   // use with  fgetpos() fsetpos()
  unsigned line_length; // optional field as code could re-compute as needed.
} line_index;

size_t line_count;
// read some lines
line_index *index = malloc(sizeof *index * line_count);
// read more lines
index = realloc(index, sizeof *index * line_count);
// edit lines, save changes to appended temporary file.
// ...
// Save file -weave the contents of the source file and temp file to the new output file.

此外,对于巨大的文件,数组line_index[]本身也可以在磁盘存储器中实现。访问很容易计算。在极端意义上,文件中只有1个随时都需要在内存中。

答案 6 :(得分:0)

你提到了状态机。每个有限状态自动机都可以被优化为具有最小(或没有)前瞻。

可以在Lex中执行此操作吗?它将生成您可以编译的输出c文件。

如果您不想使用Lex,可以随时执行以下操作:

  1. 将n个字符读入(ring?)缓冲区,其中n是模式的大小。
  2. 尝试将缓冲区与模式匹配
  3. 如果匹配goto 1
  4. 打印缓冲区[0],读取字符,转到2
  5. 对于非常长的模式和退化输入,strstr可能很慢。在这种情况下,您可能希望研究更高级的刺痛匹配算法。

答案 7 :(得分:0)

mmap()是一种处理大尺寸文件的好方法。 它为您提供了很大的灵活性,但您需要对页面大小保持谨慎。 Here是一篇很好的文章,讨论更具体的内容。