我想用 C 语言开发一个应用程序,我需要从磁盘上的文件中逐字检查。有人告诉我,从文件中读取一行然后将其拆分为单词会更有效,因为需要更少的文件访问。这是真的吗?
6 回答
如果您知道您将需要整个文件,那么您可能会尽可能大块地读取它(在极端情况下,您将一次性将整个文件内存映射到内存中)。你是对的,这是因为需要更少的文件访问。
但是,如果您的程序不慢,那么请以使其开发速度最快且最无错误的方式编写它。早期优化是一种严重的罪过。
不是真的,假设您将要使用scanf()
并且您对“单词”的定义与被scanf()
视为单词的内容相匹配。
标准 I/O 库将缓冲实际的磁盘读取,并且读取一行或一个单词在磁盘访问方面具有基本相同的 I/O 成本。如果您要使用 读取文件的大块fread()
,您可能会获得一些好处 - 以复杂性为代价。
但是对于阅读单词,很可能scanf()
还有一个保护性的字符串格式说明符,例如%99s
您的数组是否char word[100];
可以正常工作并且可能更易于编码。
如果您对 word 的定义比 支持的定义更复杂scanf()
,那么阅读行和拆分可能会更容易。
就拆分而言,在性能方面没有区别。您在一种情况下使用空格进行拆分,在另一种情况下使用换行符。
但是,它会以您需要分配缓冲区 M 次的方式影响 word,而对于行,它将是 N 次,其中 M>N。因此,如果您采用分词方法,请先尝试计算总内存需求,分配那么多块(这样您就不会得到碎片化的 M 个块),然后从该块中获取 M 个缓冲区。请注意,相同的方法可以应用于行拆分,但差异将不太明显。
这是正确的,您应该将它们读入缓冲区,然后拆分为您定义为“单词”的任何内容。唯一不正确的情况是,如果您能够fscanf()
正确地抓住您认为是单词的内容(值得怀疑)。
实际上并没有回答您的确切问题(单词与行),但如果您需要同时在内存中的所有单词,那么最有效的方法是:
- 确定文件大小
- 为整个文件分配缓冲区加上一个字节
- 将整个文件读取到缓冲区,并放入
'\0'
额外的字节。 - 跳过它并计算它有多少单词
- 分配
char*
(指向单词的指针)或int
(指向缓冲区的索引)索引数组,大小与字数匹配 - 对缓冲区进行第二次传递,并将单词首字母的地址或索引存储到索引数组中,并用
'\0'
(字符串结束标记)覆盖缓冲区中的其他字节。
如果您有足够的内存,那么假设单词数的最坏情况可能会稍微快一些:((filesize+1) / 2
一个字母单词之间有一个空格,文件中有奇数个字节)。还采用带有索引数组的 Java ArrayList 或 Qt QVector 方法,并realloc()
在字数超过当前容量时使用使其大小加倍,将非常有效(由于加倍=指数增长,重新分配不会发生多次)。
主要的性能瓶颈可能是:
- 对 stdio 文件 I/O 函数的任何调用。调用越少,开销就越少。
- 动态内存分配。应该尽量少做。最终,对 malloc 的大量调用会导致堆分段。
所以它归结为一个经典的编程考虑:你可以获得快速的执行时间,或者你可以获得低内存使用率。你不能两者兼得,但你可以找到一些在执行时间和内存消耗方面都最有效的合适的中间立场。
一种极端情况是,通过将整个文件作为一个大块读取并将其上传到动态内存,可以获得最快的执行速度。或者另一个极端,您可以逐字节读取它并在读取时对其进行评估,这可能会使程序变慢但根本不会使用动态内存。
您需要具备各种特定于 CPU 和特定于操作系统的功能的基本知识,才能最有效地优化代码。对齐、缓存内存布局、底层 API 函数调用的有效性等问题都很重要。
为什么不尝试几种不同的方法并对其进行基准测试呢?