有没有更好的方法来确定大 txt 文件(1-2 GB)中的行数? [复制]

Posted

技术标签:

【中文标题】有没有更好的方法来确定大 txt 文件(1-2 GB)中的行数? [复制]【英文标题】:Is there a better way to determine the number of lines in a large txt file(1-2 GB)? [duplicate] 【发布时间】:2016-04-01 23:26:52 【问题描述】:

我正在尝试计算 txt 文件中的所有行,我使用的是 StreamReader:

public int countLines(string path)

    var watch = System.Diagnostics.Stopwatch.StartNew();
    int nlines=0;
    string line;
    StreamReader file = new StreamReader(path);
    while ((line = file.ReadLine()) != null)
    
        nlines++;
    
    watch.Stop();
    var elapsedMs = watch.ElapsedMilliseconds;
    Console.Write(elapsedMs)
    // elapsedMs = 3520  --- Tested with a 1.2 Mill txt
    return nlines;

有没有更有效的方法来计算行数?

【问题讨论】:

这已经是最好的方法了。需要多长时间? 为避免分配然后丢弃一大堆字符串,调用file.Read() 并计算回车和/或换行字符的数量可能更有效。 如果您不需要文件内容(除了行数),您可以删除 string line 变量并执行 while (file.ReadLine() != null) nlines++; @derpirscher 虽然可能更清楚意图,但它绝对不会影响最终速度。 您的代码本质上是计算指针前进到下一个 0×0A 的次数(因为这也涵盖了 0×0c 和 0×0A 的组合)。在增加计数的同时将其作为原始指针推进运行,看看这是否提高了 StreamReader 开销的效率。我对此感到生疏,所以我邀请评论。 【参考方案1】:

我只是在这里大声思考,但性能很可能受 I/O 限制而不是 CPU 限制。无论如何,我想知道是否将文件解释为文本可能会减慢速度,因为它必须在文件的编码和string 的本机编码之间进行转换。如果您知道编码是 ASCII 或与 ASCII 兼容,那么您可能只需计算一个值为 10 的字节出现的次数(这是换行符的字符代码)就可以逃脱。

如果你有以下情况:

FileStream fs = new FileStream("path.txt", FileMode.Open, FileAccess.Read, FileShare.None, 1024 * 1024);

long lineCount = 0;
byte[] buffer = new byte[1024 * 1024];
int bytesRead;

do

    bytesRead = fs.Read(buffer, 0, buffer.Length);
    for (int i = 0; i < bytesRead; i++)
        if (buffer[i] == '\n')
            lineCount++;

while (bytesRead > 0);

我对 1.5GB 文本文件的基准测试结果,计时 10 次,取平均值:

StreamReader 接近,4.69 秒 File.ReadLines().Count() 接近,4.54 秒 FileStream 接近,1.46 秒

【讨论】:

您可以使用char 作为变量名,因为它是C# 保留关键字,也许@bytecurrentByte 会更好?您可能还会发现读取字节缓冲区的性能更高。无论哪种方式,+1 可以避免那些不必要的 string 分配。 @dreamlax 您假设新行表示为LF (\n) (10)CRLF (\r\n)。换行定义为回车\r (13)的情况呢? 我生成了一个包含 1M 行的文件,所有行都显示为“这是第 #n 行”。使用OP的方法,它在124ms内计算了1M。使用 ReadByte 方法,需要 220 毫秒。我用int newline = (int)Encoding.ASCII.GetBytes(Environment.NewLine)[0]; 存储了比较值只是想把我的结果扔出去。 我已经更新了我的帖子,我得到了一个非常不同的结果:-) 赞成。使用citiesTour_400.txt,我得到了 SR 3.754s 和 FS 2.751s。我敢肯定,如果我优化发布并可能禁用我的防病毒软件,它会更快。 :-)【参考方案2】:

您已经有了合适的解决方案,但您可以将所有代码简化为:

var lineCount = File.ReadLines(@"C:\MyHugeFile.txt").Count();

基准

我不确定dreamlax 是如何达到他的基准测试结果的,但这里有一些东西可以让任何人都可以在他们的机器上重现;你可以复制粘贴到 LINQPad 中。

首先让我们准备输入文件:

var filePath = @"c:\MyHugeFile.txt";

for (int counter = 0; counter < 5; counter++)

    var lines = new string[30000000];

    for (int i = 0; i < lines.Length; i++)
    
        lines[i] = $"This is a line with a value of: i";
    

    File.AppendAllLines(filePath, lines);

这应该会产生一个 1.5 亿行的文件,大约 6 GB。

现在让我们运行每个方法:

void Main()

    var filePath = @"c:\MyHugeFile.txt";
    // Make sure you clear windows cache!
    UsingFileStream(filePath);

    // Make sure you clear windows cache!
    UsingStreamReaderLinq(filePath);

    // Make sure you clear windows cache!
    UsingStreamReader(filePath);


private void UsingFileStream(string path)

    var sw = Stopwatch.StartNew();
    using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
    
        long lineCount = 0;
        byte[] buffer = new byte[1024 * 1024];
        int bytesRead;

        do
        
            bytesRead = fs.Read(buffer, 0, buffer.Length);
            for (int i = 0; i < bytesRead; i++)
                if (buffer[i] == '\n')
                    lineCount++;
        
        while (bytesRead > 0);       
        Console.WriteLine("[FileStream] - Read: 0:n0 in 1", lineCount, sw.Elapsed);
    


private void UsingStreamReaderLinq(string path)

    var sw = Stopwatch.StartNew();
    var lineCount = File.ReadLines(path).Count();
    Console.WriteLine("[StreamReader+LINQ] - Read: 0:n0 in 1", lineCount, sw.Elapsed);


private void UsingStreamReader(string path)

    var sw = Stopwatch.StartNew();
    long lineCount = 0;
    string line;
    using (var file = new StreamReader(path))
    
        while ((line = file.ReadLine()) != null)  lineCount++; 
        Console.WriteLine("[StreamReader] - Read: 0:n0 in 1", lineCount, sw.Elapsed);
    

结果:

[FileStream] - 读取:150,000,000 in 00:00:37.3397443

[StreamReader+LINQ] - 在 00:00:33.8842190 中读取:150,000,000

[StreamReader] - 在 00:00:34.2102178 中读取:150,000,000

更新

使用优化ON 运行会导致:

[FileStream] - 读取:150,000,000 in 00:00:18.1636374

[StreamReader+LINQ] - 读取:150,000,000 in 00:00:33.3173354

[StreamReader] - 读取:150,000,000 in 00:00:32.3530890

【讨论】:

@dreamlax 不会的!您将ReadLinesReadAllLines 混淆,前者返回IEnumerable&lt;string&gt;。参考:***.com/questions/119559/… 比 StreamReader 方式慢一点(3619 毫秒)但还是谢谢 :) @Brayan,对 IO 进行基准测试并不像运行两次代码并比较结果那么简单。特别是在处理磁盘时。至少您需要清除Windows Cached files 的内容,然后多次运行它们并取平均值。您可以使用 RAMMap 清除缓存,更多信息:***.com/questions/478340/… @dreamlax,这次我刚刚用优化 ON 更新了结果,你的方法几乎快了 2 倍 :-) 我唯一的反对意见是缺乏对 carriage return (\r) 的支持 @MaYaN:确实,您的回答要安全得多。对于具有混合行尾的文件,我的可能会给出不同的结果。我经常处理 PostScript 文件,并且经常看到混合行结尾(嵌入文件可能有一个行结尾,而整个 PostScript 文件可能有另一个)。我还经常处理 Mac(尤其是旧的 Mac),并且不时遇到带有 \r 行结尾的文件,但这种情况很少见(而且越来越少)。

以上是关于有没有更好的方法来确定大 txt 文件(1-2 GB)中的行数? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

有没有比我的实现更好的方法来检查更新的文件?

FTP传大文件又慢又麻烦,有没有更好的替代传输方案?

QFile 无法打开大文件

linux sed 使用 我有个1.sh文件内容如下 sed -i 's/$1/$2/g' 1.txt

有没有比多个子查询更好的方法来获得这个,也许有更多的连接?

有没有更好的方法来定义全局变量?