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

如果你正在使用。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

这与您自己的解决方案非常相似,不同之处是它使用固定缓冲区大小为1024的StreamReader实现。在我的计算机上,与缓冲区大小为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。因为它干净高效。如果需要特殊的共享选项(例如使用FileShare.ReadWrite),可以使用自己的代码,但应该增加缓冲区大小。


使用以下代码:

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

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

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


在Stack Overflow的问题中有一个关于这个问题的好话题,“收益返回”比“老派”返回慢吗?

它说:

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?


虽然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();
        }
    }

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