C:使用fread()/ fgets()而不是fgetc()(块I / O与字符I / O)逐行读取文本文件(带有可变长度行)

是否有使用fread (块I / O)而不是fgetc (字符I / O)的getline函数?

通过fgetc逐个字符地读取文件会有性能损失。 我们认为为了提高性能,我们可以在getline的内部循环中通过fread使用块读取。 然而,这引入了读取超过行尾的潜在不良影响。 至少,这需要实现getline来跟踪文件的“未读”部分,这需要超出ANSI C FILE语义的抽象。 这不是我们想要自己实现的东西!

我们已经分析了我们的应用程序,并且由于我们通过fgetc逐个字符地消耗大型文件,因此速度慢。 通过比较,其余的开销实际上具有微不足道的成本。 我们总是按顺序读取文件的每一行,从开始到结束,我们可以在读取期间锁定整个文件。 这可能使基于freadgetline更容易实现。

那么,是否存在使用fread (块I / O)而不是fgetc (字符I / O)的getline函数? 我们非常肯定它确实如此,但如果没有,我们应该如何实施呢?

更新发现了一篇有用的文章, 在C中处理用户输入 ,由Paul Hsieh撰写。 这是一个基于fgetc的方法,但它有一个有趣的讨论替代方案(从有多糟糕的开始,然后讨论fgets ):

另一方面,来自C程序员的常见反驳(即使是那些经验丰富的程序员)也就是说应该使用fgets()作为替代。 当然, fgets()本身并不能真正处理用户输入。 除了具有奇怪的字符串终止条件(在遇到\ n或EOF,但不是\ 0时),当缓冲区达到容量时选择终止的机制是简单地突然停止fgets()操作并且\ 0终止它。 因此,如果用户输入超过预分配缓冲区的长度, fgets()将返回部分结果。 处理这个程序员有几个选择; 1)简单地处理截断的用户输入(没有办法向用户反馈输入被截断,而他们提供输入)2)模拟一个可增长的字符数组并用连续的fgets()调用填充它。 对于可变长度的用户输入,第一个解决方案几乎总是一个非常糟糕的解决方案,因为缓冲区在大多数情况下不可避免地会过大,因为它试图捕获太多普通情况,而对于exception情况则太小。 第二种解决方案很好,只是正确实施可能很复杂。 两者都不涉及fgets在’\ 0’方面奇怪行为。

练习留给读者:为了确定通过调用fgets()确实读取了多少字节,可以尝试扫描,就像它一样,为’\ n’并跳过任何’\ 0’而不超过传递给fgets()的大小。 解释为什么这对于流的最后一行是不够的。 ftell()的弱点是什么阻止它完全解决这个问题?

练习留给读者:通过在每次调用fgets()之间用非零值覆盖整个缓冲区来解决确定fgets()消耗的数据长度的问题。

因此,使用fgets(),我们可以选择编写大量代码并使用与C库的其余部分不一致或具有任意截止的行终止条件。 如果这还不够好,那么我们还剩下什么? scanf()以无法分离的方式将解析与读取混合,并且fread()将读取超出字符串结尾的内容。 简而言之,C库没有任何东西。 我们被迫直接在fgetc()的基础上推出自己的。 让我们试一试。

那么,是否存在基于fgets (并且不截断输入)的getline函数?

不要使用fread 。 使用fgets 。 我认为这是一个家庭作业/课堂项目的问题,所以我没有提供完整的答案,但如果你说不是,我会给出更多的建议。 绝对有可能使用纯粹的fgets提供100%的GNU风格的getline语义,包括嵌入的空字节,但它需要一些聪明的思考。

好的,更新,因为这不是作业:

  • 将缓冲区设置为'\n'
  • 使用fgets
  • 使用memchr找到第一个'\n'
  • 如果未找到'\n' ,则该行比您的缓冲区长。 加入缓冲区,用'\n'填充新部分,然后将fgets填入新部分,并根据需要重复。
  • 如果'\n'后面的字符是'\0' ,则fgets因为到达行尾而终止。
  • 否则, fgets因达到EOF而终止, '\n'memset遗留下来,前一个字符是fgets写的终止空值,之前的字符是实际数据读取的最后一个字符。

如果你不关心支持嵌入空值的行,你可以消除memset并使用strlen代替memchr (无论哪种方式,null都不会终止读取;它只是你读入行的一部分)。

还有一种方法可以使用fscanf"%123[^\n]"说明符(其中123是您的缓冲区限制)执行相同的操作,这使您可以灵活地停止非换行符(ala GNU getdelim )。 但是,除非您的系统具有非常精细的scanf实现,否则它可能会很慢。

fgets和fgetc / setvbuf之间没有很大的性能差异。 尝试:

 int c; FILE *f = fopen("blah.txt","r"); setvbuf(f,NULL,_IOLBF,4096); /* !!! check other values for last parameter in your OS */ while( (c=fgetc(f))!=EOF ) { if( c=='\n' ) ... else ... }