何时应该使用 slurp 文件,何时应该逐行读取文件?

3

假设我有一个编辑文本文件的C#应用程序。每个文件使用的技术可以是以下两种之一:

1) 一次性将文件读入字符串,进行更改,并将该字符串写入现有文件:

string fileContents = File.ReadAllText(fileName);

// make changes to fileContents here...

using (StreamWriter writer = new StreamWriter(fileName))
{
    writer.Write(fileContents);
}

2) 逐行读取文件,将更改写入临时文件,然后删除源文件并重命名临时文件:

using (StreamReader reader = new StreamReader(fileName))
{
    string line;

    using (StreamWriter writer = new StreamWriter(fileName + ".tmp"))
    {
        while (!reader.EndOfStream)
        {
            line = reader.ReadLine();
            // make changes to line here
            writer.WriteLine(line);
        }
    }
}
File.Delete(fileName);
File.Move(fileName + ".tmp", fileName);

这些选项的性能考虑是什么?
在我看来,无论是按行读取还是一次性读取整个文件,读取的数据量都是相同的,磁盘时间将占主导地位而内存分配时间则不占优势。话虽如此,一旦文件被加载到内存中,操作系统就可以自由地将其分页,当这样做时,大读取的好处已经丧失了。另一方面,在使用临时文件时,一旦句柄关闭,我需要删除旧文件并重命名临时文件,这会产生一定的开销。
然后有关缓存、预读和磁盘缓冲区大小等问题......
我假设在某些情况下,一次性读取文件更好,而在其他情况下,按行操作更好。我的问题是,这两种情况的条件是什么?
1个回答

4
在某些情况下,一次读取整个文件更好;在另一些情况下,逐行操作更好。实际上,逐行读取是一个更具体的情况。我们想要区分的真正选择是ReadAll和使用缓冲区。ReadLine做出了一些假设——最大的假设是文件实际上有行,并且它们的长度是合理的!如果我们无法对文件做出这种假设,我们需要选择一个特定的缓冲区大小并读入其中,而不管是否已到达行末。因此,在阅读全部内容和使用缓冲区之间做出决策时,总是选择最容易实现和最幼稚的方法,直到你遇到一个具体的情况,该方法对你不起作用。有了具体的案例,你可以根据你实际拥有的信息做出明智的决定,而不是猜测假设情况。最简单的方法是一次性读取所有内容。当性能成为问题时?这个应用程序是否针对不可控制的文件运行,因此它们的大小是不可预测的?这里只是一些你需要将其分块的示例。

阅读单行的观察实际上是缓冲读取的特例,这一点非常敏锐。谢谢! - Justin R.
我想知道为什么像ReadAllBytes这样的方法不包括指定长度的选项,以及一个选项参数来指示如何处理比该长度长或短的文件(如果较短:填充、返回实际大小或抛出异常;如果较长,则返回前导部分或抛出异常)?在许多情况下,应用程序会有一些最大合理大小的概念,尝试读取超过此大小的内容最好干净地抛出异常,而不是导致可能破坏其他无关线程的内存不足错误。 - supercat
@supercat,已经有一个使用OpenRead的结构存在,它返回一个流,你可以很容易地控制每次读取多少数据到缓冲区。ReadAllBytes更像是边缘情况,它只是作为一个帮助程序存在。 - Rex M
@RexM:我知道它作为一个辅助方法存在,但在常规使用中,如果文件恰好很大,人们会想让该方法吞掉两个G吗?当然有一些情况下,人们希望尽可能地读取所有内容,但我认为这种情况比期望更小的文件要少得多。在我看来,ReadAllBytes展示了与gets()相同的懒惰,并且只是稍微不那么邪恶(内存不足错误不像随机内存覆盖那样糟糕,但仍然会导致崩溃)。 - supercat
@supercat,感谢您的反馈,但考虑到我不在CLR团队中,也许我不是将其发送给最佳人选 ;) - Rex M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接