使用 TStreamReader 和 TStringList 处理非常大的文本文件

Ric*_*oks 5 delphi text tstringlist readline delphi-10.2-tokyo

我正在使用 Embarcadero 的 Rad Studio Delphi (10.2.3),并且在读取非常大的文本文件(700 万行以上,每行都不同,行长度可以是 1 到 ~200 个字符等)时遇到内存问题。我对 Delphi 编程相当陌生,所以在发帖之前我已经搜索过 SO 和 Google 寻求帮助。

我最初实现了一个 TStringList 并使用 LoadFromFile 方法读取文件,但是当处理的文本文件变得足够大时,这会失败。然后,我实现了一个 TStreamReader 并使用 ReadLn 使用此处找到的基本代码填充 TStringList:

TStringList.LoadFromFile - 大文本文件的异常

代码示例:

//MyStringList.LoadFromFile(filename);
Reader := TStreamReader.Create(filename, true);
try
  MyStringList.BeginUpdate;
  try
    MyStringList.Clear;
    while not Reader.EndOfStream do
      MyStringList.Add(Reader.ReadLine);
  finally
    MyStringList.EndUpdate;
  end;
finally
  Reader.Free;
end;
Run Code Online (Sandbox Code Playgroud)

在我需要处理的文件变得巨大(约 700 万行以上)之前,这种方法一直很有效。看起来 TStringList 变得太大以至于内存不足。我说“出现”是因为我实际上无权访问正在运行的文件,并且所有错误信息都是由我的客户通过电子邮件提供的,这使得这个问题变得更加困难,因为我无法简单地在 IDE 中调试它。

该代码是32位编译的,我无法使用64位编译器。我也不能包括数据库系统之类的。不幸的是,我有一些严格的限制。我需要加载每一行以查找模式并将这些行与其他行进行比较以查找“模式中的模式”。我很抱歉在这里说得很含糊。

底线是——有没有一种方法可以在不使用 TStringList 的情况下访问文本文件中的每一行,或者也许有更好的方法来处理 TStringList 内存?

也许有一种方法可以将 StreamReader 中的特定行块加载到 TStringList 中(例如,读取前 100,000 行并处理,接下来的 100,000 行等),而不是一次加载所有内容?我想我可以写一些东西来处理可能的“块间”模式。

预先感谢您的任何和所有帮助和建议!

***** 编辑更新 *****

好的,这是我需要实现的基本解决方案:

var
  filename: string;
  sr: TStreamReader;
  sl: TStringList;
  total, blocksize: integer;
begin
  filename := 'thefilenamegoeshere';
  sl := TStringList.Create;
  sr := TStreamReader.Create(filename, true);
  sl.Capacity := sr.BaseStream.Size div 100;
  total := 0; // Total number of lines in the file (after it is read in)
  blocksize := 10000; // The number of lines per "block"
  try
    sl.BeginUpdate;
    try
      while not sr.EndOfStream do
        begin
          sl.Clear;
          while not (sl.Count >= blocksize) do
            begin
              sl.Add(sr.ReadLine);
              total := total + 1;
              if (sr.EndOfStream = true) then break;
            end;
          // Handle the current block of lines here
        end;
    finally
      sl.EndUpdate;
    end;
  finally
    sr.Free;
    sl.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

我有一些测试代码,我将用它们来完善我的例程,但这似乎相对快速、高效且足够。我要感谢大家的回应,让我的灰质兴奋不已!

lok*_*oki 0

作为(非常)快速的修复,您可以尝试使用https://github.com/Zeus64/alcinoe中的 TALStringlist (只需在代码中将 TStringList 替换为 TalStringList) 。这不是一个非常干净的方法,但 TALStringlist 将保留在 unicode UTF-8 中,将默认 UTF 16 字符串使用的内存减少 2。由于您有 7 000 000 行,大约 100 个字符,这意味着大约 700 Mb,这可以在 32 位上运行