我需要读取一行文本(以换行符作为终止符),但不能假设其长度。因此,我现在面临两种可能性:
- 使用
fgets
函数,并每次检查最后一个字符是否是换行符并将其连续附加到缓冲区中 - 使用
fgetc
函数逐个读取每个字符,不时使用realloc
重新分配缓冲区大小
直觉告诉我fgetc
变体可能会更慢,但是我不知道fgets
如何在不检查每个字符的情况下完成此操作(另外我的直觉并不总是那么好)。由于行很长,因此性能很重要。
我想知道每种方法的优缺点。谢谢。
fgets()
和动态内存分配相结合的方式,或者您可以研究一下在POSIX 2008标准中提供的接口getline()
,它在较新的Linux机器上可用并为您处理内存分配。您需要跟踪缓冲区长度以及其地址 - 因此,您甚至可以创建一个结构来处理这些信息。fgetc()
也可以工作,但稍微麻烦一些 - 但仅仅是稍微麻烦一些。在底层,它使用与fgets()
相同的机制。当直接调用fgetc()
时,内部可能能够利用更快速的操作 - 类似于strchr()
- 这些操作在调用fgetc()
时不可用。getline(3)
函数?如果是,我建议你使用它。我看到的最大优点是它可以自行分配缓冲区(如果需要),并且如果传入的缓冲区太小,则会对其进行realloc()
操作。(因此,这意味着你需要传入从malloc()
获取的内容。)这消除了使用fgets/fgetc时的一些痛苦,并且你可以希望实现它的C库编写者已经考虑到了效率问题。奖励:Linux上的man页面有一个很好的例子展示如何高效地使用它。getline()
的功能很好;但是 getline()
这个名称对用户命名空间来说是一个可怕的侵入,抢占了更广泛使用的函数名称之一(例如,参见 K&R 1 和 2),并提供了各种不同的接口。使用那个名称是一个令人震惊的决定;提供该功能是一个极好的决定。唯一令人惊讶的是忽略了处理 CRLF 行尾的能力;相关的 getdelim()
函数可以处理 CR 或 LF 或 NUL 行尾,但无法处理 CRLF 行尾。 - Jonathan Lefflergetc
而不是fgetc
。标准试图将其实现为宏以避免函数调用开销。getc
被实现为宏时,getc
和fgets
之间的差异通常非常小,因此您最好集中精力处理其他问题。如果您可以设置最大行长度,即使很大,那么一个fgets
就可以解决问题。如果不能,则多个fgets
调用仍然比多个fgetc
调用更快,因为后者的开销更大。
不过,更好的答案是,在必要之前,不值得担心性能差异。如果fgetc
足够快,那又有什么关系呢?
getc
通常被实现为宏,因此比fgetc
更快速,并且只要你小心(参数不能是表达式),就应该使用它。 - mk12我会分配一个大缓冲区,然后使用fgets进行读取,检查、重新分配并重复操作,直到读取到行尾。
每次读取(无论是通过fgetc还是fgets)都会进行系统调用,这需要时间,你希望尽量减少这种情况发生的次数,因此调用fgets的次数较少并在内存中迭代会更快。
如果你从文件中读取,另一种选择是使用mmap()
映射文件。
fgets
实现getline
函数的一个限制是无法同时处理空字节和文件不以换行符结尾的情况。如果fgets
遇到EOF条件并返回时没有换行符,您只能假设字符串在第一个空字节处结束。(在其他情况下,您可以使用strchr(buf,'\n')
查找读取停止的位置 - 或者如果没有'\n'
,则需要realloc
。) - mk12fgets()
函数不适用于处理包含空字节的文件——恰恰因为它不能可靠地指示已读取多少个字节。如果您的数据文件包含空字节,则(可能)不应使用fgets()
函数来读取它。 - Jonathan Leffler'\n'
以外的定界符时,它可能会有用才被提到。 - mk12