14

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

例如这三个:
c# 如何计算
文本文件中的行数 确定文本文件中的行
数 如何快速计算行数?

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

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()方法仍然是最快的方法之一......老实说,在答案中的所有评论和讨论之后,我感到困惑。

文件 #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;
}
4

7 回答 7

9

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

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

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

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

于 2013-01-09T17:51:06.550 回答
5

知道如何快速做到这一点的最好方法是考虑在不使用 C/C++ 的情况下最快的方法。

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

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

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

于 2013-01-09T17:57:29.240 回答
4

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

读取单个字节的方法比其他方法慢约 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();
        }
于 2013-01-09T21:20:05.333 回答
3
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;
}
于 2013-01-09T17:52:47.847 回答
3

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

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

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

于 2013-01-09T18:18:46.603 回答
1

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

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

这个页面对几种不同的技术进行了很好的性能比较,包括使用 BufferedReaders、读入 StringBuilder 对象和整个数组。

于 2014-09-06T02:16:38.340 回答
0

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

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

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

    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);
于 2016-07-29T22:14:45.497 回答