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

时间:2010-12-10 17:00:01

标签: c file-io fgets fread

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

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

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

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

更新 Paul Hsieh发现了一篇有用的文章Handling User Input in C。这是一种基于fgetc的方法,但它对备选方案进行了有趣的讨论(从fgetc有多糟糕开始,然后讨论gets):

  

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

     

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

     

练习留给读者:通过在每次调用 fgets()所消耗数据长度的问题>与fgets()

     

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

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

2 个答案:

答案 0 :(得分:5)

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

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

  • memset您的缓冲区为'\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实现,否则它可能很慢。

答案 1 :(得分:1)

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
    ...
}