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