【问题标题】:Is StreamReader.Readline() really the fastest method to count lines in a file?StreamReader.Readline() 真的是计算文件中行数的最快方法吗?
【发布时间】:2013-01-09 17:45:16
【问题描述】:

环顾四周,我发现了很多关于如何计算文件中行数的讨论。

例如这三个:
c# how do I count lines in a textfile
Determine the number of lines within a text file
How to count lines fast?

所以,我继续并最终使用了我能找到的最有效(至少在内存方面?)的方法:

private static int countFileLines(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
        int i = 0;
        while (r.ReadLine() != null) 
        { 
            i++; 
        }
        return i;
    }
}

但是当文件中的行本身很长时,这需要很长时间。真的没有更快的解决方案吗?

我一直在尝试使用StreamReader.Read()StreamReader.Peek(),但我不能(或不知道如何)让它们中的任何一个在出现“东西”后立即进入下一行(字符?文本?)。

有什么想法吗?


结论/结果(根据提供的答案运行一些测试后):

我在两个不同的文件上测试了下面的 5 种方法,我得到了一致的结果,这似乎表明普通的旧 StreamReader.ReadLine() 仍然是最快的方法之一......老实说,在所有 cmets 之后我都感到困惑并在答案中讨论。

文件 #1:
大小:3,631 KB
行数:56,870

文件 #1 的结果(以秒为单位):
0.02 --> ReadLine 方法。
0.04 --> 读取方法。
0.29 --> ReadByte 方法。
0.25 --> Readlines.Count 方法。
0.04 --> ReadWithBufferSize 方法。

文件 #2:
大小:14,499 KB
行数:213,424

文件 #1 的结果(以秒为单位):
0.08 --> ReadLine 方法。
0.19 --> 读取方法。
1.15 --> ReadByte 方法。
1.02 --> Readlines.Count 方法。
0.08 --> ReadWithBufferSize 方法。

根据收到的所有反馈,我测试了以下 5 种方法:

private static int countWithReadLine(string filePath)
{
    using (StreamReader r = new StreamReader(filePath))
    {
    int i = 0;
    while (r.ReadLine() != null)
    {
        i++;
    }
    return i;
    }
}

private static int countWithRead(string filePath)
{
    using (StreamReader _reader = new StreamReader(filePath))
    {
    int c = 0, count = 0;
    while ((c = _reader.Read()) != -1)
    {
        if (c == 10)
        {
        count++;
        }
    }
    return count;
    }            
}

private static int countWithReadByte(string filePath)
{
    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    int b;

    b = s.ReadByte();
    while (b >= 0)
    {
        if (b == 10)
        {
        i++;
        }
        b = s.ReadByte();
    }
    return i;
    }
}

private static int countWithReadLinesCount(string filePath)
{
    return File.ReadLines(filePath).Count();
}

private static int countWithReadAndBufferSize(string filePath)
{
    int bufferSize = 512;

    using (Stream s = new FileStream(filePath, FileMode.Open))
    {
    int i = 0;
    byte[] b = new byte[bufferSize];
    int n = 0;

    n = s.Read(b, 0, bufferSize);
    while (n > 0)
    {
        i += countByteLines(b, n);
        n = s.Read(b, 0, bufferSize);
    }
    return i;
    }
}

private static int countByteLines(byte[] b, int n)
{
    int i = 0;
    for (int j = 0; j < n; j++)
    {
    if (b[j] == 10)
    {
        i++;
    }
    }

    return i;
}

【问题讨论】:

  • read() 或 peek() 如何知道下一行在流中的位置?
  • @John 通过查找 \n\r 字符。
  • 每一行的字节数是否完全相同,或者几乎完全相同?如果它们是准确的,你可以只计算文件大小,如果它们很接近,你可以根据平均行长度得出一个接近的近似值。
  • 我指的是关于让流向前跳转的评论
  • @John:谢谢,约翰。你的回答(?)帮助我意识到我看错了地方,即使你的意思是讽刺。

标签: c# streamreader readline lines peek


【解决方案1】:

不,不是。重点是 - 它实现了不需要的字符串。

要计算它,最好忽略“字符串”部分并转到“行”部分。

LINE 是一系列以 \r\n (13, 10 - CR LF) 或其他标记结尾的字节。

在缓冲流中沿着字节运行,计算行尾标记的出现次数。

【讨论】:

  • 您能用一些示例代码详细说明一下吗?谢谢!
  • 如果不是学校或其他实习生的作业,那你是编程错误。这是“编程入门”测试的级别。
  • 我想我在这方面处于那个水平,我不会生气。我不是在这里寻找“免费午餐”,我只是不喜欢人们在不知道完整故事的情况下就评判你。我非常感谢您的“答案”,并且在我不断尝试解决这个问题时,绝对会利用它来发挥我的优势……无论有没有代码示例。谢谢。
  • 查看 Brian 的代码示例并重新阅读您的答案让我意识到发生了什么。我还怀疑当我说“你能详细说明一下......?”时,我可能听起来“不欣赏”。由于您的答案实际上解释了一些事情,我最终可能会选择它作为这个问题的答案。谢谢你,TomTom。
  • TomTom,我进行了一些测试,显然我的原始方法 Readline() 似乎是最快的。我将我的笔记添加到我的原始问题中,以防您对此有更多反馈。谢谢!
【解决方案2】:

了解如何快速做到这一点的最佳方法是考虑不使用 C/C++ 的最快方法。

在汇编中有一个 CPU 级别的操作会扫描内存中的字符,因此在汇编中您可以执行以下操作

  • 将文件的大部分(或全部)读入内存
  • 执行 SCASB 命令
  • 根据需要重复

因此,在 C# 中,您希望编译器尽可能接近这一点。

【讨论】:

    【解决方案3】:

    我尝试了多种方法并测试了它们的性能:

    读取单个字节的方法比其他方法慢约 50%。其他方法都返回大约相同的时间。您可以尝试创建线程并异步执行此操作,因此在等待读取时,您可以开始处理先前的读取。这听起来让我很头疼。

    我会选择一个班轮:File.ReadLines(filePath).Count();,它的性能与我测试的其他方法一样好。

            private static int countFileLines(string filePath)
            {
                using (StreamReader r = new StreamReader(filePath))
                {
                    int i = 0;
                    while (r.ReadLine() != null)
                    {
                        i++;
                    }
                    return i;
                }
            }
    
            private static int countFileLines2(string filePath)
            {
                using (Stream s = new FileStream(filePath, FileMode.Open))
                {
                    int i = 0;
                    int b;
    
                    b = s.ReadByte();
                    while (b >= 0)
                    {
                        if (b == 10)
                        {
                            i++;
                        }
                        b = s.ReadByte();
                    }
                    return i + 1;
                }
            }
    
            private static int countFileLines3(string filePath)
            {
                using (Stream s = new FileStream(filePath, FileMode.Open))
                {
                    int i = 0;
                    byte[] b = new byte[bufferSize];
                    int n = 0;
    
                    n = s.Read(b, 0, bufferSize);
                    while (n > 0)
                    {
                        i += countByteLines(b, n);
                        n = s.Read(b, 0, bufferSize);
                    }
                    return i + 1;
                }
            }
    
            private static int countByteLines(byte[] b, int n)
            {
                int i = 0;
                for (int j = 0; j < n; j++)
                {
                    if (b[j] == 10)
                    {
                        i++;
                    }
                }
    
                return i;
            }
    
            private static int countFileLines4(string filePath)
            {
                return File.ReadLines(filePath).Count();
            }
    

    【讨论】:

    • 非常感谢您抽出宝贵时间对此进行测试,尼克!我将使用您给出的示例进行一些测试,最终使用似乎更有效/更快的示例。再次感谢!
    • 尼克,我根据我运行的一些测试,使用你提出的大多数方法,在我原来的问题中添加了一些结论 cmets,以防你想看看并有更多反馈。再次感谢!
    • 是的,我使用的文件要大得多 1GB。
    【解决方案4】:
    public static int CountLines(Stream stm)
    {
        StreamReader _reader = new StreamReader(stm);
        int c = 0, count = 0;
        while ((c = _reader.Read()) != -1)
        {
            if (c == '\n')
            {
                count++;
            }
        }
        return count;
    }
    

    【讨论】:

    • 这很可能有可怕的性能,为文件中的每个字符调用一个方法。
    • @500-InternalServerError 你怎么能做得更好?我看不到任何可能的解决方法。你能做的最好的事情就是隐藏某些 API 正在做同样的事情的事实。考虑到每一行都相当大,它的内存占用比具有相似性能的 OP 更小。
    • @Servy - 我至少会使用一个缓冲区并一次读入一个块。另一种方法是使用文件映射,但我还没有从 .NET 中尝试过。
    • @500-InternalServerError 操作系统和/或硬盘将在内部缓冲它,您不太可能看到再次缓冲它的任何好处。
    • @Servy - 我非常不同意,但承认差异对 OP 来说可能无关紧要。
    【解决方案5】:

    是的,从任何实际意义上来说,这样的阅读方式都是最快、最简单的方法。

    这里没有捷径。文件不是基于行的,因此您必须从文件中读取每个字节以确定有多少行。

    正如 TomTom 所指出的,创建字符串并不是绝对需要计算行数,但大部分时间都花在等待从磁盘读取数据上。编写一个更复杂的算法可能会节省百分之一的执行时间,并且会大大增加编写和测试代码的时间。

    【讨论】:

    • 请注意,变化不会在速度上那么大,而是在内存占用上。如果行很大,则将每一行存储在内存中与一次仅将一个字符存储在内存中之间的区别(尽管使用缓冲,情况并非如此,但这意味着内存占用量几乎完全是大小缓冲区,仅此而已)。
    • @Servy:是的,但这对速度影响很小。
    • 是的,这就是为什么我打开说它不会影响速度。
    【解决方案6】:

    有很多方法可以读取文件。通常,最快的方式是最简单的:

    using (StreamReader sr = File.OpenText(fileName))
    {
            string s = String.Empty;
            while ((s = sr.ReadLine()) != null)
            {
                   //do what you gotta do here
            }
    }
    

    This page does a great performance comparison 介于几种不同的技术之间,包括使用 BufferedReaders、读入 StringBuilder 对象以及读入整个数组。

    【讨论】:

    • K.I.S.S.最佳原则。
    【解决方案7】:

    StreamReader 通常不是读取文件的最快方式,因为将字节编码为字符的开销很小,因此读取字节数组中的文件更快。
    由于缓存和其他进程,我每次得到的结果都有些不同,但这是我用 16 MB 文件得到的结果之一(以毫秒为单位):

    75 ReadLines 
    82 ReadLine 
    22 ReadAllBytes 
    23 Read 32K 
    21 Read 64K 
    27 Read 128K 
    

    一般File.ReadLines 应该比StreamReader.ReadLine 循环慢一点。 File.ReadAllBytes 文件较大时速度较慢,并且文件较大时会抛出内存不足异常。 FileStream 的默认缓冲区大小是 4K,但在我的机器上 64K 似乎是最快的。

        private static int countWithReadLines(string filePath)
        {
            int count = 0;
            var lines = File.ReadLines(filePath);
    
            foreach (var line in lines) count++;
            return count;
        }
    
        private static int countWithReadLine(string filePath)
        {
            int count = 0;
            using (var sr = new StreamReader(filePath))      
                while (sr.ReadLine() != null)
                    count++;
            return count;
        }
    
        private static int countWithFileStream(string filePath, int bufferSize = 1024 * 4)
        {
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                int count = 0;
                byte[] array = new byte[bufferSize];
    
                while (true)
                {
                    int length = fs.Read(array, 0, bufferSize);
    
                    for (int i = 0; i < length; i++)
                        if(array[i] == 10)
                            count++;
    
                    if (length < bufferSize) return count;
                }
            } // end of using
        }
    

    并通过以下方式测试:

    var path = "1234567890.txt"; Stopwatch sw; string s = "";
    File.WriteAllLines(path, Enumerable.Repeat("1234567890abcd", 1024 * 1024 )); // 16MB (16 bytes per line)
    
    sw = Stopwatch.StartNew(); countWithReadLines(path)   ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLines \n";
    sw = Stopwatch.StartNew(); countWithReadLine(path)    ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLine \n";
    sw = Stopwatch.StartNew(); countWithReadAllBytes(path); sw.Stop(); s += sw.ElapsedMilliseconds + " ReadAllBytes \n";
    
    sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 32); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 32K \n";
    sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 64); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 64K \n";
    sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 *128); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 128K \n";
    
    MessageBox.Show(s);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-17
      • 1970-01-01
      • 1970-01-01
      • 2013-11-27
      • 2013-01-23
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多