[英]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大小的限制,而是受环境允许您为程序分配和使用的内存量的限制。 这取决于各种因素:
您的代码中还有其他问题:
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系统,并且拥有相当数量的交换空间,那么就打开整个坏孩子吧。 它会消耗你的内存并消耗硬盘空间(交换)。 因此,你可以让整个东西立刻打开,只是不是所有的东西都在公羊上。
优点
缺点
考虑将文件视为外部线阵列。
代码可以使用行索引数组。 该索引数组可以以大文件大小的一小部分保存在内存中。 通过此查找快速完成对任何行的访问,使用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,您可以随时执行以下操作:
对于非常长的模式和退化输入,strstr也可能很慢。 在这种情况下,您可能希望研究更高级的刺痛匹配算法。
mmap()是处理大尺寸文件的一种非常好的方法。 它为您提供了很大的灵活性,但您需要对页面大小保持谨慎。 这是一篇很好的文章,讨论更具体的内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.