【问题标题】:What's the fastest way to count the total lines of text file in c#?在 c# 中计算文本文件总行数的最快方法是什么?
【发布时间】:2013-11-05 17:12:03
【问题描述】:

我的代码是:

    int linenumber = File.ReadLines(path).Count();

但是大约 1 gig 大小的文件需要很长时间(大约 20 秒)。

那么有人知道解决这个问题的更好方法吗?

更新 6:

我已经测试了您的解决方案:

对于大约 870 mb 大小的文件:

方法一:{my codetime(seconds) : 13}

方法2:(来自MarcinJuraszek & Locke)(同){

time(seconds) : 12}

方法3:(来自Richard Deeming){time(seconds) : 19}

方法4:(来自user2942249){time(seconds) : 13}

方法5:(来自Locke){time(seconds) : 13lineBuffer = {4096 , 8192 , 16384 , 32768} }相同

方法6:(来自Locke edition 2){time(seconds) : 9Buffer size = 32KBtime(seconds) : 10Buffer size = 64KB}

正如我所说,在我的评论中,有一个应用程序 (native code) 可以在我的电脑中的5 second 中打开此文件。因此这是not about h.d.d speed

By Compiling MSIL to Native Code,区别was not obvious

Conclusion:此时,Locke method 2faster,而不是其他方法。

所以我将他的帖子标记为Answer。但是如果有人find better idea,此帖子将打开。

我为帮助我的亲爱的朋友们 +1 vote upto solve the problem

感谢您的帮助。有趣的更好的主意。 最好的祝福 聪明人

【问题讨论】:

  • 您可以一次只加载一个文件块,然后将每个块中的换行符数相加。
  • 根据您的硬盘速度,读取 1 gig 文件实际上可能需要很长时间,在这种情况下,您会受到 I/O 限制,并且没有编码技术 会提高速度。
  • @RobertHarvey,碎片化甚至可能在其中发挥作用。
  • @RobertHarvey:OP 已经在使用标记为重复的解决方案,并且发现它不够,因为该问题正在寻找“最简单”的方法来计算行号,而 OP 想要“最快”。这些问题的答案截然不同。
  • @SmartMan:只要确保你做好功课。你不能真的指望别人为你写你的应用程序。

标签: c# line-numbers


【解决方案1】:

以下是一些可以快速完成的方法:

StreamReader:

using (var sr = new StreamReader(path))
{
    while (!String.IsNullOrEmpty(sr.ReadLine()))
        lineCount ++;
}

文件流:

var lineBuffer = new byte[65536]; // 64Kb
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read,
       FileShare.Read, lineBuffer.Length))
{
    int readBuffer = 0;
    while ((readBuffer = fs.Read(lineBuffer, 0, lineBuffer.Length)) > 0)
    {
        for (int i = 0; i < readBuffer; i++)
        {
            if (lineBuffer[i] == 0xD) // Carriage return + line feed
                lineCount++;
        }
    }
}

多线程:

可以说线程数不应该影响读取速度,但现实世界的基准测试有时可以证明并非如此。尝试不同的缓冲区大小,看看你的设置是否有任何收获。 *此方法包含竞争条件。谨慎使用。

var tasks = new Task[Environment.ProcessorCount]; // 1 per core
var fileLock = new ReaderWriterLockSlim();
int bufferSize = 65536; // 64Kb

using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read,
        FileShare.Read, bufferSize, FileOptions.RandomAccess))
{
    for (int i = 0; i < tasks.Length; i++)
    {
        tasks[i] = Task.Factory.StartNew(() =>
            {
                int readBuffer = 0;
                var lineBuffer = new byte[bufferSize];

                while ((fileLock.TryEnterReadLock(10) && 
                       (readBuffer = fs.Read(lineBuffer, 0, lineBuffer.Length)) > 0))
                {
                    fileLock.ExitReadLock();
                    for (int n = 0; n < readBuffer; n++)
                        if (lineBuffer[n] == 0xD)
                            Interlocked.Increment(ref lineCount);
                }
            });
    }
    Task.WaitAll(tasks);
}

【讨论】:

  • 刚刚对其进行了测试,我看不出执行时间有上下差异,但是这可能会随着文件的增大而改变。不过这很有趣,因为我认为我需要 FileShare.Read 线程才能一起处理同一个文件。
  • FileStream 不是为同时从多个线程访问而设计的。您的多线程代码不一定工作
  • @Servy,虽然我同意你的观点,但到目前为止它似乎正在开展小规模测试。如果您有一些了解,您可以解释为什么会发生性能提升,我很乐意倾听。
  • @Locke 代码有竞争条件。这并不意味着它永远不会起作用,它意味着它有时会起作用,有时会不起作用;您永远无法知道运行程序时会得到什么。如果它不起作用,则性能特征无关。一个快 10 倍的程序如果不能提供正确的答案,它仍然是无用的。
  • @Servy,如果我刚刚进行的更新满足避免竞争条件,请告诉我。
【解决方案2】:

这取决于硬件,一个问题是最佳缓冲区大小是多少。也许等于或大于磁盘扇区大小。在进行了自己的实验之后,我发现通常最好让系统来确定。如果速度确实是一个问题,您可以下拉到 Win32 API ReadFile/CreateFile 指定各种标志和参数,例如异步 IO 和无缓冲、顺序读取等......这可能有助于提高性能,也可能不会。您必须进行概要分析并查看在您的系统上最有效的方法。在 .NET 中,您可以固定缓冲区以获得更好的性能,当然,在 GC 环境中固定内存还有其他后果,但如果您不将其保留太久,等等...

    const int bufsize = 4096;
    int lineCount = 0;
    Byte[] buffer = new Byte[bufsize];
    using (System.IO.FileStream fs = new System.IO.FileStream(@"C:\\data\\log\\20111018.txt", FileMode.Open, FileAccess.Read, FileShare.None, bufsize))
    {
        int totalBytesRead = 0;
        int bytesRead;
        while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) {
            int i = 0;
            while (i < bytesRead)
            {
                switch (buffer[i])
                {
                    case 10:
                        {
                            lineCount++;
                            i++;
                            break;
                        }
                    case 13:
                        {
                            int index = i + 1;
                            if (index < bytesRead)
                            {
                                if (buffer[index] == 10)
                                {
                                    lineCount++;
                                    i += 2;
                                }
                            }
                            else
                            {
                                i++;
                            }
                            break;
                        }
                    default:
                        {
                            i++;
                            break;
                        }
                }
            }
            totalBytesRead += bytesRead;
        }
        if ((totalBytesRead > 0) && (lineCount == 0))
            lineCount++;                    
    }

【讨论】:

    【解决方案3】:

    正如您的测试所示,代码更改不会对速度产生重大影响。瓶颈在于您的磁盘读取数据,而不是处理数据的 C# 代码。

    如果您想加快执行此任务的速度,请购买更快/更好的硬盘驱动器,无论是具有更高 RPM 的硬盘,还是固态硬盘。或者,您可以考虑使用 RAID0,这可能会提高您的磁盘读取速度。

    另一种选择是拥有多个硬盘驱动器,并分解文件以便每个驱动器存储一部分,然后您可以将工作与处理每个驱动器上的文件的任务并行化。 (请注意,当您只有一个磁盘时并行化工作不会有任何帮助,而且实际上更有可能造成伤害。)

    【讨论】:

    • 谢谢,但正如我所说,有一个应用程序 UltraEdit 可以在 5 秒内在我的电脑中打开此文件。因此这与硬盘速度无关。
    • @SmartMan 这并不意味着它实际上已经加载了文件的全部内容。它可以依靠分页和不同的执行来不将整个文件的内容加载到内存中。
    • 当然,它使用最少的内存。那么它如何在 5 秒内打开并计算总行数? (我的主要问题是只计算行号)
    • 嗯,我收到了关于 UltaEdit 文件加载技术的回复,但总行数出现得如此之快很有趣。
    • 另外,您是如何衡量其他应用程序的性能的?如果您的计时机制只是您计数,那么延迟几秒钟并非不可能。如果您之前加载过文件,它也可能会缓存有关行号的信息。
    【解决方案4】:

    假设构建一个字符串来表示每一行是需要时间的,这样的事情可能会有所帮助:

    public static int CountLines1(string path)
    {
       int lineCount = 0;
       bool skipNextLineBreak = false;
       bool startedLine = false;
       var buffer = new char[16384];
       int readChars;
    
       using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, buffer.Length))
       using (var reader = new StreamReader(stream, Encoding.UTF8, false, buffer.Length, false))
       {
          while ((readChars = reader.Read(buffer, 0, buffer.Length)) > 0)
          {
             for (int i = 0; i < readChars; i++)
             {
                switch (buffer[i])
                {
                   case '\n':
                   {
                      if (skipNextLineBreak)
                      {
                         skipNextLineBreak = false;
                      }
                      else
                      {
                         lineCount++;
                         startedLine = false;
                      }
                      break;
                   }
                   case '\r':
                   {
                      lineCount++;
                      skipNextLineBreak = true;
                      startedLine = false;
                      break;
                   }
                   default:
                   {
                      skipNextLineBreak = false;
                      startedLine = true;
                      break;
                   }
                }
             }
          }
       }
    
       return startedLine ? lineCount + 1 : lineCount;
    }
    

    编辑 2:
    他们所说的“假设”是真的!为每个字符调用.Read() 的开销超过了不为每一行创建字符串所节省的成本。即使更新代码一次读取一个字符块仍然比原来的方法慢。

    【讨论】:

    • reader.Peek() 也会放缓。如果你可以在不偷看的情况下做到这一点,那会更快。也许将读取放在方法的末尾而不是开始(并且在 13 的情况下不调用它,因为它已经完成了读取)
    • @ScottChamberlain: StreamReader 在读取数据时缓冲数据(至少在 .NET 4.5 中),因此 Peek() 后跟 Read() 应该不会产生任何显着差异。
    • 还要注意他不想计算空行;这不考虑这一点。
    • @SmartMan:我很惊讶这这么慢。我想这是不为每一行创建字符串和为每个字符调用Read 方法之间的权衡。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 2011-11-15
    • 2011-12-23
    • 2013-01-23
    相关资源
    最近更新 更多