关于c#:逐行读取文本文件的最快方法是什么?

What's the fastest way to read a text file line-by-line?

我想一行一行地阅读文本文件。我想知道我是否在.NET C工作范围内尽可能高效地完成这项工作。

这就是我目前正在尝试的:

1
2
3
4
5
6
7
8
9
10
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
}


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

使用streamreader.readline

这基本上是你的方法。出于某种原因,您将缓冲区大小设置为可能的最小值(128)。增加这一点通常会提高性能。默认大小为1024,其他好的选择是512(Windows中的扇区大小)或4096(NTFS中的群集大小)。您必须运行一个基准来确定最佳缓冲区大小。一个更大的缓冲区——如果不是更快的话——至少不会比一个更小的缓冲区慢。

1
2
3
4
5
6
7
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

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

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

使用file.readalllines

这与前面的方法非常相似,只是这个方法增加了用于创建返回的行数组的字符串列表,因此内存需求更高。但是,它返回String[],而不是IEnumerable,允许您随机访问线路。

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

使用string.split

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

1
2
3
4
5
6
7
using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("

"
.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

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


如果您使用的是.NET 4,只需使用File.ReadLines,这一切都是为您完成的。我怀疑它和你的差不多,只是它也可能使用FileOptions.SequentialScan和更大的缓冲区(128似乎很小)。


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

如果你只是想在不做太多事情的情况下读取一个文件中的行,根据这些基准,读取文件的最快方法是古老的方法:

1
2
3
4
5
6
7
8
using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

但是,如果您需要对每行做很多工作,那么本文总结出最好的方法如下(如果您知道要读多少行,那么预先分配一个字符串[]会更快):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
});

使用以下代码:

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

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

它以消耗内存为代价,但完全值得!


关于这个栈内溢出问题有一个很好的话题,"收益率回报"比"老派"回报慢吗?.

它说:

ReadAllLines loads all of the lines into memory and returns a
string[]. All well and good if the file is small. If the file is
larger than will fit in memory, you'll run out of memory.

ReadLines, on the other hand, uses yield return to return one line at
a time. With it, you can read any size file. It doesn't load the whole
file into memory.

Say you wanted to find the first line that contains the word"foo",
and then exit. Using ReadAllLines, you'd have to read the entire file
into memory, even if"foo" occurs on the first line. With ReadLines,
you only read one line. Which one would be faster?


如果文件大小不大,则读取所有文件的速度更快,然后拆分字符串:

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


如果您想使用现有的API来读取这些行,就不能再快了。但是读取更大的块并在读取缓冲区中手动查找每一行可能会更快。


如果您有足够的内存,我发现通过将整个文件读取到内存流中,然后打开一个流阅读器来读取行,可以获得一些性能提升。只要你真的打算读整个文件,这会带来一些改进。