繁体   English   中英

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

[英]How to parse files that cannot fit entirely in memory RAM

我已经创建了一个框架来解析合适大小的文本文件,这些文件可以放在内存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;
}

如果您希望坚持使用当前的设计,可以选择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,存在类似的机制。

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

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

您的方法存在多个问题。

最大可用内存的概念并不那么明显:从技术上讲,您不受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"); 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;
}

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

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

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

std::ifstream

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

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

优点

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

缺点

  • 没有

考虑将文件视为外部线阵列。

代码可以使用行索引数组。 该索引数组可以以大文件大小的一小部分保存在内存中。 通过此查找快速完成对任何行的访问,使用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 需要随时存储在内存中。

你提到了状态机。 每个有限状态自动机都可以进行优化,以实现最小(或没有)前瞻。

在Lex可以这样做吗? 它将生成您可以编译的输出c文件。

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

  1. 将n个字符读入(ring?)缓冲区,其中n是模式的大小。
  2. 尝试将缓冲区与模式匹配
  3. 如果匹配转到1
  4. 打印缓冲区[0],读取字符,转到2

对于非常长的模式和退化输入,strstr也可能很慢。 在这种情况下,您可能希望研究更高级的刺痛匹配算法。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM