【问题标题】:What's the fastest way to read a text file line-by-line?逐行读取文本文件的最快方法是什么?
【发布时间】:2011-12-23 15:17:45
【问题描述】:

我想逐行读取文本文件。我想知道我是否在 .NET C# 范围内尽可能高效地做到这一点。

这是我目前正在尝试的:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}

【问题讨论】:

  • Fastest 你的意思是从性能或开发的角度来看?
  • 这将在方法执行期间锁定文件。您可以使用 File.ReadAllLines 到一个数组中,然后处理该数组。
  • 顺便说一句,将filestream = new FileStream 括在using() 语句中,以避免锁定文件句柄时可能出现的烦人问题
  • 关于封闭 FileStream 是 using() 语句,有关推荐方法请参见 StackOverflow:StackOverflow using statement filestream streamreader
  • 我认为 ReadToEnd() 更快。

标签: c# .net performance file-io text-files


【解决方案1】:

要找到逐行读取文件的最快方法,您必须进行一些基准测试。我在我的电脑上做了一些小测试,但你不能指望我的结果适用于你的环境。

使用 StreamReader.ReadLine

这基本上是你的方法。出于某种原因,您将缓冲区大小设置为可能的最小值 (128)。增加此值通常会提高性能。默认大小为 1,024,其他不错的选择是 512(Windows 中的扇区大小)或 4,096(NTFS 中的簇大小)。您必须运行基准测试以确定最佳缓冲区大小。更大的缓冲区——如果不是更快的话——至少不会比更小的缓冲区慢。

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

FileStream 构造函数允许您指定FileOptions。例如,如果您从头到尾顺序读取一个大文件,您可能会受益于FileOptions.SequentialScan。同样,基准测试是您能做的最好的事情。

使用 File.ReadLines

这与您自己的解决方案非常相似,只是它是使用StreamReader 实现的,固定缓冲区大小为 1,024。在我的计算机上,与缓冲区大小为 128 的代码相比,这会导致性能稍好一些。但是,您可以通过使用更大的缓冲区大小来获得相同的性能提升。此方法使用迭代器块实现,不会消耗所有行的内存。

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

使用 File.ReadAllLines

这很像前面的方法,只是这个方法增加了一个字符串列表,用于创建返回的行数组,因此内存要求更高。但是,它返回 String[] 而不是 IEnumerable<String>,允许您随机访问这些行。

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

使用 String.Split

这种方法相当慢,至少在大文件上(在 511 KB 文件上测试),可能是由于 String.Split 的实现方式。它还为所有行分配一个数组,与您的解决方案相比,增加了所需的内存。

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

我的建议是使用File.ReadLines,因为它既干净又高效。如果您需要特殊的共享选项(例如您使用FileShare.ReadWrite),您可以使用您自己的代码,但您应该增加缓冲区大小。

【讨论】:

  • 感谢您 - 您在 StreamReader 的构造函数中包含缓冲区大小参数真的很有帮助。我正在从 Amazon 的 S3 API 流式传输,并且使用匹配的缓冲区大小与 ReadLine() 结合使用会大大加快速度。
  • 我不明白。理论上,读取文件所花费的大部分时间将是磁盘上的查找时间和操作流的开销,就像您对 File.ReadLines 所做的那样。另一方面,File.ReadLines 应该一次性将文件的所有内容读入内存。性能怎么会差?
  • 我不能说速度性能,但有一件事是肯定的:内存消耗要差得多。如果您必须处理非常大的文件(例如 GB),这非常关键。甚至更多,如果这意味着它必须交换内存。在速度方面,您可以添加 ReadAllLine 需要在返回结果延迟处理之前读取所有行。在某些情况下,速度的印象比原始速度更重要。
  • 如果您将流作为字节数组读取,它将以 20%~80% 的速度读取文件(来自我所做的测试)。您需要的是获取字节数组并将其转换为字符串。我就是这样做的:对于阅读使用 stream.Read() 您可以创建一个循环以使其分块读取。将整个内容附加到字节数组(使用 System.Buffer.BlockCopy)后,您需要将字节转换为字符串:Encoding.Default.GetString(byteContent,0,byteContent.Length - 1 ).Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
  • 请在所有区块中与 { } 共享代码?
【解决方案2】:

如果您使用的是 .NET 4,只需使用 File.ReadLines 即可。我怀疑它与您的非常相同,除了它还可能使用FileOptions.SequentialScan 和更大的缓冲区(128 似乎非常小)。

【讨论】:

  • ReadLines() 的另一个好处是它很懒,因此可以很好地与 LINQ 配合使用。
  • 在 foreach 循环中使用时,File.ReadLines 是否在每次迭代后关闭文件?
  • @RBT: 否 - 它会在迭代器被释放时关闭文件。 (它实际上在其他方面有些损坏,但这是另一回事,只有在您尝试多次使用它时才有意义。)
【解决方案3】:

虽然File.ReadAllLines() 是读取文件的最简单方法之一,但它也是最慢的方法之一。

如果您只是想读取文件中的行而不做太多事情,according to these benchmarks,读取文件的最快方法是古老的方法:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

但是,如果您必须对每一行做很多事情,那么this article 得出的结论是最好的方法如下(如果您知道要执行多少行,那么预先分配一个字符串 [] 会更快)阅读):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});

【讨论】:

    【解决方案4】:

    使用以下代码:

    foreach (string line in File.ReadAllLines(fileName))
    

    这是阅读性能的巨大差异。

    这是以消耗内存为代价的,但完全值得!

    【讨论】:

    【解决方案5】:

    如果文件不大,那么读取整个文件然后再拆分会更快

    var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                                  StringSplitOptions.RemoveEmptyEntries);
    

    【讨论】:

    • @jgauffin 我不知道 file.ReadAlllines() 的实现背后,但我认为它的缓冲区有限,fileReadtoEnd 缓冲区应该更大,因此通过这种方式访问​​文件的次数将减少, 并且在文件不大的情况下做 string.Split 比多次访问文件要快。
    • 我怀疑File.ReadAllLines 的缓冲区大小是固定的,因为文件大小是已知的。
    • @jgauffin:在 .NET 4.0 中,File.ReadAllLines 创建一个列表并使用StreamReader.ReadLine 在循环中添加到该列表中(可能会重新分配底层数组)。此方法使用默认缓冲区大小 1024。StreamReader.ReadToEnd 避免了行解析部分,如果需要,可以在构造函数中设置缓冲区大小。
    • 在文件大小方面定义“BIG”会很有帮助。
    【解决方案6】:

    Stack Overflow 问题中有一个很好的主题Is 'yield return' slower than "old school" return?

    上面写着:

    ReadAllLines 将所有行加载到内存中并返回一个 细绳[]。如果文件很小,一切都很好。如果文件是 大于内存容量,就会耗尽内存。

    另一方面,ReadLines 使用 yield return 返回一行 一次。有了它,您可以读取任何大小的文件。它不会加载整个 文件到内存中。

    假设您要查找包含单词“foo”的第一行, 然后退出。使用 ReadAllLines,您必须阅读整个文件 到内存中,即使“foo”出现在第一行。使用 ReadLines, 你只读了一行。哪个会更快?

    【讨论】:

      【解决方案7】:

      如果你有足够的内存,我发现通过将整个文件读入memory stream,然后在其上打开一个流阅读器来读取行,可以提高性能。只要您确实打算阅读整个文件,这就会产生一些改进。

      【讨论】:

      • File.ReadAllLines 似乎是一个更好的选择。
      【解决方案8】:

      如果您想使用现有的 API 来读取这些行,您将无法获得更快的速度。但是读取更大的块并在读取缓冲区中手动查找每个新行可能会更快。

      【讨论】:

        猜你喜欢
        • 2021-12-08
        • 2011-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-10
        • 2012-08-26
        相关资源
        最近更新 更多