【问题标题】:Fast way to get total line number of a large file快速获取大文件总行数的方法
【发布时间】:2020-12-14 10:02:04
【问题描述】:

我正在处理大型文本文件(大于 100MB)。我需要尽可能快的总行数。我目前正在使用下面的代码(更新:添加了 try-finally):

var
  SR: TStreamReader;
  totallines: int64;
  str: string;
begin
  SR:=TStreamReader.Create(myfilename, TEncoding.UTF8);
  try
    totallines:=0;
    while not SR.EndOfStream do
    begin
      str:=SR.ReadLine;
      inc(totallines);
    end;
  finally
    SR.Free;
  end;
end;

有没有更快的方法来获得总分?

【问题讨论】:

  • 您必须使用try..finally 块来保护您的对象。但是要回答您的问题:可能最快的方法是将其作为二进制文件读取,然后遍历其字节并计算您找到的 CRLF 序列的数量。您上面的代码很慢,因为您不仅要计算行数,还要将它们提取为字符串。
  • 大于 100MB 并不能说明什么。您如何处理这将在很大程度上取决于您所说的比 100MB 大多少。管理 1GB 文件的方式与管理 100GB 文件的方式大不相同。
  • 假设 10GB 是最大值。有趣的是任务管理器在执行上面的代码时显示 CPU 使用率很高,但磁盘使用率很低。
  • 是的,因为您正在分配内存以在计数时将文件的每一行复制到一个字符串中。
  • @dummzeuch:OP 使用的是 UTF-8,所以如果你找到一个字节 10,你就知道它是一个 LF。对于 CR 也是如此。请参阅en.wikipedia.org/wiki/UTF-8 处的表格。

标签: delphi streamreader delphi-10.3-rio


【解决方案1】:
Program LineCount;

{$APPTYPE CONSOLE}
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
{$SetPEFlags 1}

{ Compile with XE8 or above... }

USES
  SysUtils,
  BufferedFileStream;

VAR
  LineCnt: Int64;
  Ch: Char;
  BFS: TReadOnlyCachedFileStream;

function Komma(const S: string; const C: Char = ','): string;
{ About 4 times faster than Comma... }
var
  I: Integer; // loops through separator position
begin
  Result := S;
  I := Length(S) - 2;
  while I > 1 do
  begin
    Insert(C, Result, I);
    I := I - 3;
  end;
end; {Komma}

BEGIN
  writeln('LineCount - Copyright (C) 2020 by Walter L. Chester.');
  writeln('Counts lines in the given textfile.');
  if ParamCount <> 1 then
    begin
      writeln('USAGE:  LineCount <filename>');
      writeln;
      writeln('No file size limit!  Counts lines: takes 4 minutes on a 16GB file.');
      Halt;
    end;
  if not FileExists(ParamStr(1)) then
    begin
      writeln('File not found!');
      halt;
    end;
  writeln('Counting lines in file...');
  BFS := TReadOnlyCachedFileStream.Create(ParamStr(1), fmOpenRead);
  try
    LineCnt := 0;
    while BFS.Read(ch,1) = 1 do
      begin
        if ch = #13 then
          Inc(LineCnt);
        if (LineCnt mod 1000000) = 0 then
          write('.');
      end;
    writeln;
    writeln('Total Lines: ' + Komma(LineCnt.ToString));
  finally
    BFS.Free;
  end;
END.

【讨论】:

  • 非常感谢!有趣的是,Int64 不起作用。所以我把它改成了整数。另外,我发现 Delphi 有一个内置的 TBufferedFileStream 类,它工作得更好一些。不管怎样,你的回答是正确的,打开了我的视野。
  • 这是整个程序的代码。似乎工作得很好,而且速度很快:
【解决方案2】:

答案很简单:不。您的算法是最快的,但实现不是。您必须阅读整个文件并计算行数。至少在线条不是固定大小的情况下。

您阅读文件的方式可能会影响全局性能。 在尽可能大的二进制缓冲区(字节数组)中逐块读取文件。然后计算缓冲区中的行数,并与同一缓冲区中的块循环。

【讨论】:

  • 答案其实是肯定的。它可以比这段代码更快,因为流阅读器很慢。
  • 我会简单地分配一个固定的缓冲区,或者对文件进行内存映射,然后按块扫描原始文件数据,计算 LF 字节数,甚至不用费心解码 UTF-8。
  • @RemyLebeau 实际上 LF 远远不够,请参阅 Unicode line terminators。即使没有 UTF-8 上下文,我也希望 old Mac newline of CR alone.
  • @AmigoJack OP 正在使用 TStreamReader.ReadLine(),它仅支持 bare-CR、bare-LF 和 CRLF 换行符。手动扫描换行符时处理这三个并不难。如今,在实际数据中使用其他 Unicode 换行符的情况很少见。但如果你需要处理所有这些,那真的没那么难。
猜你喜欢
  • 2021-01-01
  • 1970-01-01
  • 2012-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 2019-11-24
  • 2012-05-02
相关资源
最近更新 更多