【问题标题】:How do I process a text file in C by chunks of lines?如何按行块处理 C 中的文本文件?
【发布时间】:2016-03-08 23:05:14
【问题描述】:

我正在用 C 语言编写一个程序,它处理一个文本文件并跟踪每个唯一的单词(通过使用一个结构体,该结构体具有一个单词的 char 数组和一个出现次数的计数)并将这个结构体存储到一种数据结构。但是,作业包括:“整个 txt 文件可能非常大,无法保存在主内存中。请在您的程序中考虑这一点。”

课后我问他,他说一次读取文本文件 X 行(我认为他的建议是 20,000 行?),分析它们并更新结构,直到你读完文件。

谁能帮助解释执行此操作的最佳方法并告诉我要使用哪些功能?我对 C 非常非常陌生。

(我当前的程序对于小文件是准确和正确的,我只需要让它适应大文件)。

非常感谢!!

编辑:

        fp = fopen(argv[w], "r");
        if ((fp) == NULL){
           fprintf( stderr, "Input file %s cannot be opened.\n", argv[w] );
         return 2;
        }

        /* other parts of my program here */

        char s[MaxWordSize];

        while (fscanf(fp,"%s",s) != EOF){   
            nonAlphabeticDelete(s); // removes non letter characters

            toLowerCase(s); //converts the string to lowercase

            //attempts to add to data structure 
            pthread_mutex_lock(&lock);
            add(words, &q, s);
            pthread_mutex_unlock(&lock);
        }

这行得通,我只需要将它调整为一次通过文本文件走 X 行。

【问题讨论】:

  • 分享您的一些代码,以便我们为您提供更好的答案...
  • 好像老师要你分配20k的内存块,用fread一次读取那么多字节然后处理那个块。
  • 这看起来您并没有将整个文件读入内存?我认为大文本文件不会有问题。
  • @maxton 我和我的教授之间存在严重的语言障碍,这就是他给我的,所以这就是我必须得到的所有信息...... :(
  • 我认为您的教授可能假设您会将整个文件读入内存,然后对其进行解析。但是您一次只能从磁盘中读取一个单词。读取 20k 行然后解析它们会降低程序的内存效率!

标签: c file text chunks


【解决方案1】:

这最好通过阅读一些手册来完成,但我可以提供一个开端。

FILE *fp;
fp=fopen("fileToRead.txt", "rb");
if (!fp) { /* handle failure! */ }
#define GUESS_FOR_LINE_LENGTH 80
char sentinel = '\0';
while ((sentinel = getc(fp)) != EOF)
{
    ungetc(sentinel, fp);
    char buffer[20000*GUESS_FOR_LINE_LENGTH];
    size_t numRead = fread(buffer, 1, 20000*GUESS_FOR_LINE_LENGTH, fp);
    if (numRead < 20000*GUESS_FOR_LINE_LENGTH) { /*last run */ }
    /* now buffer has numRead characters */
    size_t lastLine = numRead - 1;
    while (buffer[lastLine] != '\n') { --lastLine; }
    /* process up to lastLine */
    /* copy the remainder from lastLine to the front */
    /* and fill the remainder from the file */
}

这真的更像是伪代码。由于您通常有一个工作程序,因此您应该以此为指导。

【讨论】:

  • 不要使用feof()。阅读:stackoverflow.com/questions/5431941/…
  • 文件末尾的未定义行为 (numRead == 0)
  • 如果根本无法正常工作:使 sentinel 成为 int 变量。在堆栈上分配兆字节不是一个好主意,使用malloc 分配缓冲区更好用mmap
  • sentinel 不应该是一个 int 以便它可以正确存储 EOF 吗?
  • 以二进制模式打开文件是个好主意,您还应该读取系统页面大小的倍数的块:而不是 20000 使用 16384 或更高的幂2 并使用malloc 分配缓冲区,并有一些额外的松弛来处理部分行。
【解决方案2】:

首先尝试一次读一行。扫描行缓冲区的字边界并微调字数统计部分。使用哈希表来存储单词和计数似乎是一个好方法。将输出设为可选,以便您测量读取/解析/查找性能。

然后制作另一个程序,它对核心部分使用相同的算法,但使用mmap 读取文件的相当大的部分并扫描内存块。棘手的部分是处理块边界。

比较两个程序在一组大文件上的输出,确保计数相同。您可以通过多次连接同一个文件来创建大文件。

也比较时间。使用time 命令行实用程序。禁用此基准的输出以专注于读取/解析/分析部分。

将时间与其他程序(例如wccat - &gt; /dev/null)进行比较。一旦你获得了类似的性能,瓶颈就是从海量存储中读取的速度,没有太大的改进空间。

编辑:查看您的代码,我有以下评论:

  • fscanf 可能不是正确的工具:至少您应该保护缓冲区溢出。 foo,bar1字还是2字应该怎么处理?

  • 我建议使用 fgets()fread 并沿缓冲区移动指针,跳过非字字节,通过 256 字节数组间接将字字节转换为小写,避免复制。

  • 通过预处理器变量使锁定内容成为可选。如果words 结构仅由单个线程访问,则不需要。

  • 你是如何实现add的?什么是q

【讨论】:

    【解决方案3】:

    getline() 怎么样? 这是手册页中的一个示例http://man7.org/linux/man-pages/man3/getline.3.html

       #define _GNU_SOURCE
       #include <stdio.h>
       #include <stdlib.h>
    
       int
       main(void)
       {
           FILE *stream;
           char *line = NULL;
           size_t len = 0;
           ssize_t read;
    
           stream = fopen("/etc/motd", "r");
           if (stream == NULL)
               exit(EXIT_FAILURE);
    
           while ((read = getline(&line, &len, stream)) != -1) {
               printf("Retrieved line of length %zu :\n", read);
               printf("%s", line);
           }
    
           free(line);
           fclose(stream);
           exit(EXIT_SUCCESS);
       }
    

    【讨论】:

    • getline 是一个很好的函数,但它受到与fgets 相同的限制 - 在它返回之前您最多可以一次读取一行。它有一个名为 getdelim 的合作伙伴,它允许读取多行,或者可能读取整个文档,具体取决于分隔符。另一种选择是使用 fread
    • 只读取一行就足够了,因为目的只是为了克服限制“整个txt文件可能非常大,无法保存在主内存中”。甚至不需要读一整行——原来的fscanf(fp,"%s",s) 已经很好了。 stdio 缓冲充分注意性能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多