如何最简单地统计ASCII文件中的换行符?

19
哪种方法是获取ASCII文件行数最快的方法?

1
请将以下与编程有关的内容从英语翻译成中文。只返回翻译后的文本:例如 .txt 文件,基本上我需要换行符。 - Sunscreen
5个回答

23

通常使用 fgets 在 C 中读取文件。你也可以使用 scanf("%[^\n]"),但是很多人读这段代码时可能会感到困惑和陌生。

编辑:另一方面,如果你只想计算行数,稍微修改一下 scanf 的方法也可以非常好地完成:

while (EOF != (scanf("%*[^\n]"), scanf("%*c"))) 
    ++lines;
这样做的好处是,在每次转换中都有一个 '*',scanf 读取并匹配输入,但不对结果进行任何操作。这意味着我们不必浪费内存来存储我们不关心的行的内容(并且仍然有机会获得比那更大的行,因此除非我们进行了更多的工作以确定我们读取的输入是否以换行符结尾,否则我们的计数会出错)。
不幸的是,我们必须像这样将 scanf 分成两部分。当转换失败时,scanf 停止扫描,并且如果输入包含空行(两个连续的换行符),我们希望第一项转换失败。即使失败了,我们也希望第二个转换发生,以读取下一个换行符并继续下一行。因此,我们尝试进行第一个转换来“吃掉”行的内容,然后进行 %c 转换以读取换行符(我们真正关心的部分)。我们继续执行这两个操作,直到第二个 scanf 调用返回 EOF(通常在文件末尾,但也可能在读取错误的情况下发生)。
编辑2:当然,还有另一种可能性,即(至少可以说是)更简单、更易于理解的方法:
int ch;

while (EOF != (ch=getchar()))
    if (ch=='\n')
        ++lines;
唯一一点有些令人感到不直观的是,ch必须定义为int而不是char才能使代码正常工作。

我可以使用以下代码进行循环,直到fgets返回NULL为止:while(fgets(szTmp, 256, pfFile)) nLines++; - Sunscreen
非常赞!这个答案(1)详细解释了代码的所有操作以及如何处理输入情况,(2)通过不使用任何缓冲区避免了所有失败情况,(3)展示了“scanf”系列的罕见正确用法。 - R.. GitHub STOP HELPING ICE
我必须补充说明的是(至少在大多数系统上),这些都没有任何区别 -- 计算(比方说)1MB文件中的行数所需时间与从磁盘读取相同量的数据所需时间几乎无法分辨。使用getc、getchar、fread、scanf等函数也不会造成任何可测量的差异。 - Jerry Coffin
@vlabrecque:不仅仅是scanf——fread和fwrite也有很大的作用(而且从技术上讲,它并不是针对“read(3)”进行的,因为它不是UNIX,但它是最接近的等效物)。 - Jerry Coffin
1
我认为你可能忽略了一个特殊情况 - 如果文件的最后一行没有以换行符结尾怎么办? - caf
显示剩余11条评论

5
这是一个基于fgetc()的解决方案,适用于任何长度的行,并且不需要你分配缓冲区。
#include <stdio.h>

int main()
{
    FILE                *fp = stdin;    /* or use fopen to open a file */
    int                 c;              /* Nb. int (not char) for the EOF */
    unsigned long       newline_count = 0;

        /* count the newline characters */
    while ( (c=fgetc(fp)) != EOF ) {
        if ( c == '\n' )
            newline_count++;
    }

    printf("%lu newline characters\n", newline_count);
    return 0;
}

我已经尝试了无数种方法来计算上述所有方法中的换行符,而你的是唯一有效的!所以谢谢你。 - Maheen Siddiqui

2
也许我有所遗漏,但为什么不简单地这样做呢:

#include <stdio.h>
int main(void) {
  int n = 0;
  int c;
  while ((c = getchar()) != EOF) {
    if (c == '\n')
      ++n;
  }
  printf("%d\n", n);
}

如果您想计算部分行(即 [^\n]EOF):

#include <stdio.h>
int main(void) {
  int n = 0;
  int pc = EOF;
  int c;
  while ((c = getchar()) != EOF) {
    if (c == '\n')
      ++n;
    pc = c;
  }
  if (pc != EOF && pc != '\n')
    ++n;
  printf("%d\n", n);
}

1
在我看来,这是最好的 getchar() 答案,因为它处理了最后一行没有以 '\n' 结尾的情况。建议进行轻微简化:int pc = '\n'; while (..) { ...} if (pc != '\n') ++n; - chux - Reinstate Monica

2

为什么要比较所有字符呢?这样会非常慢。在10MB的文件中,需要大约3秒钟。
下面是更快的解决方案。

unsigned long count_lines_of_file(char *file_patch) {
    FILE *fp = fopen(file_patch, "r");
    unsigned long line_count = 0;

    if(fp == NULL){
        return 0;
    }
    while ( fgetline(fp) )
        line_count++;

    fclose(fp);
    return line_count;
}

这取决于线路的长度。对于我的任务来说,它快了大约400倍。 - Krzysztof Szewczyk
为什么它更快?fgetline()的内部实现也必须比较每个字符以找到换行符... - Max Snijders
在实践中,我得到了如此不同的结果。 - Krzysztof Szewczyk
readahead 和多线程会有所区别(以及真正的 aio 文件系统) - scheiflo
2
注意:fgetline()不在C99或C11规范中。 - chux - Reinstate Monica
1
如果指针为NULL,这是否意味着文件未找到或其他原因?无论如何,这意味着文件一开始就没有被打开。为什么需要调用fclose?(我只确定在C++中适用。C语言也是这样吗?)如果指针为NULL,这是否意味着文件未找到或其他原因?无论如何,这意味着文件一开始就没有被打开。为什么需要调用fclose?(我只确定在C++中适用。C语言也是这样吗?) - Arc676

1

这个怎么样?

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

#define BUFFER_SIZE 4096

int main(int argc, char** argv)
{
    int count;
    int bytes;
    FILE* f;
    char buffer[BUFFER_SIZE + 1];
    char* ptr;

    if (argc != 2 || !(f = fopen(argv[1], "r")))
    {
        return -1;
    }

    count = 0;
    while(!feof(f))
    {
        bytes = fread(buffer, sizeof(char), BUFFER_SIZE, f);
        if (bytes <= 0)
        {
            return -1;
        }

        buffer[bytes] = '\0';
        for (ptr = buffer; ptr; ptr = strchr(ptr, '\n'))
        {
            ++count;
            ++ptr;
        }
    }

    fclose(f);

    printf("%d\n", count - 1);

    return 0;
}

没有理由缓冲stdio的缓冲输入。此外,这将在空文件上报告-1。 - vlabrecque
不建议这样做。它会在任何长度为BUFFER_SIZE的倍数的文件上(包括空文件,如@vlabrecque所指出的)以-1退出。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接