我已经创建了一个框架来解析合适大小的文本文件,这些文件可以放在内存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;
}
答案 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大小的限制,而是受环境允许的内存量的限制你分配和使用你的程序。这取决于各种因素:
您的代码还有其他问题:
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系统,并且拥有相当数量的交换空间,那么就打开整个坏孩子吧。它会消耗你的内存并消耗硬盘空间(交换)。因此,您可以立即打开整个物品,但不是所有物品都在撞锤上。
赞成
缺点
答案 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,可以随时执行以下操作:
对于非常长的模式和退化输入,strstr可能很慢。在这种情况下,您可能希望研究更高级的刺痛匹配算法。
答案 7 :(得分:0)
mmap()是一种处理大尺寸文件的好方法。 它为您提供了很大的灵活性,但您需要对页面大小保持谨慎。 Here是一篇很好的文章,讨论更具体的内容。