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

我想逐行读取文本文件。我想知道我是否在。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
}
546225 次浏览

如果你使用的是。net 4,只需使用File.ReadLines就可以了。我怀疑它的和你的一样,除了它也可能使用FileOptions.SequentialScan和一个更大的缓冲区(128似乎很小)。

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

如果文件大小不大,那么读取整个文件并随后拆分它会更快

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

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

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

使用StreamReader。ReadLine

这基本上就是你的方法。由于某种原因,您将缓冲区大小设置为最小值(128)。增加这个值通常会提高性能。默认大小是1024,其他好的选择是512 (Windows中的扇区大小)或4096 (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。同样,基准测试是您所能做的最好的事情。

使用文件。readline

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

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

使用文件。ReadAllLines

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

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

使用字符串。分裂

这个方法相当慢,至少在大文件上(在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),你可以使用你自己的代码,但你应该增加缓冲区的大小。

使用以下代码:

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

这是阅读表现的巨大差异。

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

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

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

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

然而,如果你必须对每一行进行大量处理,那么这篇文章得出的结论是,最好的方法如下(如果你知道要读取多少行,那么预先分配一个字符串[]会更快):

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

当你需要有效地读取和处理巨大的文本文件时,ReadLines()和ReadAllLines()可能会抛出内存不足异常,这是我的情况。另一方面,单独阅读每一行会花费很长时间。解决方案是按块读取文件,如下所示。

类:

    //can return empty lines sometimes
class LinePortionTextReader
{
private const int BUFFER_SIZE = 100000000; //100M characters
StreamReader sr = null;
string remainder = "";


public LinePortionTextReader(string filePath)
{
if (File.Exists(filePath))
{
sr = new StreamReader(filePath);
remainder = "";
}
}


~LinePortionTextReader()
{
if(null != sr) { sr.Close(); }
}


public string[] ReadBlock()
{
if(null==sr) { return new string[] { }; }
char[] buffer = new char[BUFFER_SIZE];
int charactersRead = sr.Read(buffer, 0, BUFFER_SIZE);
if (charactersRead < 1) { return new string[] { }; }
bool lastPart = (charactersRead < BUFFER_SIZE);
if (lastPart)
{
char[] buffer2 = buffer.Take<char>(charactersRead).ToArray();
buffer = buffer2;
}
string s = new string(buffer);
string[] sresult = s.Split(new string[] { "\r\n" }, StringSplitOptions.None);
sresult[0] = remainder + sresult[0];
if (!lastPart)
{
remainder = sresult[sresult.Length - 1];
sresult[sresult.Length - 1] = "";
}
return sresult;
}


public bool EOS
{
get
{
return (null == sr) ? true: sr.EndOfStream;
}
}
}

使用示例:

    class Program
{
static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("multifind.exe <where to search> <what to look for, one value per line> <where to put the result>");
return;
}


if (!File.Exists(args[0]))
{
Console.WriteLine("source file not found");
return;
}
if (!File.Exists(args[1]))
{
Console.WriteLine("reference file not found");
return;
}


TextWriter tw = new StreamWriter(args[2], false);


string[] refLines = File.ReadAllLines(args[1]);


LinePortionTextReader lptr = new LinePortionTextReader(args[0]);
int blockCounter = 0;
while (!lptr.EOS)
{
string[] srcLines = lptr.ReadBlock();
for (int i = 0; i < srcLines.Length; i += 1)
{
string theLine = srcLines[i];
if (!string.IsNullOrEmpty(theLine)) //can return empty lines sometimes
{
for (int j = 0; j < refLines.Length; j += 1)
{
if (theLine.Contains(refLines[j]))
{
tw.WriteLine(theLine);
break;
}
}
}
}


blockCounter += 1;
Console.WriteLine(String.Format("100 Mb blocks processed: {0}", blockCounter));
}
tw.Close();
}
}

我相信拆分字符串和数组处理可以显著改善, 但是这里的目标是最小化磁盘读取的数量