逐行读取文本文件的最快方法是什么?


318

我想逐行阅读文本文件。我想知道我是否在.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
}

7
通过Fastest您从性能还是发展前景呢?
sll

1
这将在方法期间锁定文件。您可以使用File.ReadAllLines进入数组,然后处理该数组。
凯尔

17
顺便说一句,filestream = new FileStreamusing()语句中括起来,以避免文件锁定句柄可能引起的烦人问题
sll

关于封闭FileStream正在using()语句,请参见StackOverflow有关推荐的方法:使用语句filestream streamreader的StackOverflow
deegee

我认为ReadToEnd()更快。
丹·吉福德

Answers:


315

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

使用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的A来实现。在我的计算机上,与缓冲区大小为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),则可以使用自己的代码,但应增加缓冲区的大小。


1
谢谢您-在StreamReader的构造函数中包含buffer size参数确实很有帮助。我从Amazon的S3 API中流式传输,并且使用匹配的缓冲区大小与ReadLine()一起可以大大加快处理速度。
理查德·K。2013年

我不明白 从理论上讲,读取文件所花费的大部分时间将是磁盘上的查找时间以及操作流的开销,就像您对File.ReadLines所做的那样。另一方面,File.ReadLines应该一次将文件的所有内容读取到内存中。表现如何会更糟?
h9uest 2015年

2
我不能说速度性能,但可以肯定的是:这在内存消耗方面要差得多。如果必须处理非常大的文件(例如GB),这非常关键。如果这意味着必须交换内存,则更多。在速度方面,您可以添加ReadAllLine需要在返回结果延迟处理之前读取所有行。在某些情况下,速度的印象比原始速度更重要。
bkqc 2016年

如果您将流作为字节数组读取,它将以20%〜80%的速度(根据我所做的测试)读取文件。您需要获取字节数组并将其转换为字符串。我就是这样做的:要使用stream.Read() 进行读取,您可以进行循环以使其分块读取。将全部内容附加到字节数组(使用System.Buffer.BlockCopy)后,您需要将字节转换为字符串:Encoding.Default.GetString(byteContent,0,byteContent.Length-1).Split(new string [ ] {“ \ r \ n”,“ \ r”,“ \ n”},StringSplitOptions.None);
金·拉格

200

如果您使用的是.NET 4,只需使用File.ReadLines它即可为您完成全部工作。我怀疑它与您的大同小异,只是它可能还会使用FileOptions.SequentialScan并且使用更大的缓冲区(128个看起来很小)。


ReadLines()它的另一个好处是它很懒,因此可以与LINQ一起很好地工作。
stt106

35

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

根据这些基准,如果您只是想读取文件中的行而没有做太多事情,则读取文件的最快方法是使用以下方法:

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

但是,如果您必须对每一行做很多事情,那么本文总结出最好的方法是执行以下操作(如果您知道要读取多少行,则可以更快地预分配一个string []):

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
});


5

关于Stack Overflow问题,有一个很好的话题:“收益率收益”是否比“旧派”收益率慢?

它说:

ReadAllLines将所有行加载到内存中并返回一个string []。如果文件很小,一切都很好。如果文件大于内存大小,则会用完内存。

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

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


4

如果文件大小不大,则读取整个文件然后将其拆分的速度会更快

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

6
File.ReadAllLines()
jgauffin 2011年

@jgauffin我不知道file.ReadAlllines()的实现背后,但我认为它的缓冲区有限,fileReadtoEnd缓冲区应该更大,因此通过这种方式减少对文件的访问次数,并进行string.Split如果文件大小不大,则比多次访问文件要快。
Saeed Amiri

File.ReadAllLines由于文件大小已知,我怀疑缓冲区大小是否固定。
jgauffin 2011年

1
@jgauffin:在.NET 4.0中File.ReadAllLines创建一个列表,并使用StreamReader.ReadLine(可能重新分配底层数组)在循环中添加到此列表。此方法使用默认的缓冲区大小1024。StreamReader.ReadToEnd避免行分析部分,并且可以根据需要在构造函数中设置缓冲区大小。
Martin Liversage

在文件大小方面定义“ BIG”将很有帮助。
保罗

2

如果您有足够的内存,则可以通过将整个文件读入内存流中,然后在该流上打开流读取器来读取行,从而发现性能有所提高。只要您实际计划以任何方式读取整个文件,这都会带来一些改进。


1
File.ReadAllLines似乎是一个更好的选择。
jgauffin 2011年

2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.