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

本文关键字:方法 是什么 读取 取文本 文件 逐行 | 更新日期: 2023-09-27 18:13:38

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

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

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

使用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

这与您自己的解决方案非常相似,除了它是使用固定缓冲区大小为1,024的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.ReadLines,因为它干净高效。如果您需要特殊的共享选项(例如您使用FileShare.ReadWrite),您可以使用自己的代码,但是您应该增加缓冲区大小。

如果您正在使用。net 4,只需使用File.ReadLines,它就可以为您完成这一切。我怀疑它的与您的相同,除了它也可能使用FileOptions.SequentialScan和更大的缓冲区(128似乎非常小)。

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

使用以下代码:

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

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

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

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

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

在Stack Overflow问题中有一个很好的主题"yield return"比"old school"return慢吗?

它说:

ReadAllLines将所有行加载到内存中并返回astring[]。如果文件小,那就好了。如果文件是

如果大于内存容量,将会耗尽内存。另一方面,

ReadLines使用yield return返回一行一段时间。有了它,您可以读取任何大小的文件。它不会加载整个文件进入内存

假设你想找到包含单词"foo"的第一行,然后退出。如果使用ReadAllLines,则必须读取整个文件进入内存,即使"foo"出现在第一行。readline,你只读了一行。哪一个会更快?

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

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

当您需要有效地读取和处理HUGE文本文件时,ReadLines()和ReadAllLines()可能会抛出Out of Memory异常,这就是我的情况。另一方面,单独阅读每一行将花费很长时间。解决方案是按块读取文件,如下所示:

类:

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

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