【问题标题】:TStringList.LoadFromFile - Exceptions with Large Text FilesTStringList.LoadFromFile - 大文本文件的异常
【发布时间】:2014-11-19 02:28:14
【问题描述】:

我正在运行 Delphi RAD Studio XE2。

我有一些非常大的文件,每个文件都包含大量行。线条本身很小 - 只有 3 个制表符分隔的双打。我想使用TStringList.LoadFromFile 将文件加载到TStringList 中,但这会引发大文件的异常。

对于 200 万行(大约 1GB)的文件,我得到 EIntOverflow 异常。对于较大的文件(例如 2000 万行和大约 10GB),我得到 ERangeCheck 异常。

我有 32GB 的 RAM 可以玩,我只是想加载这个文件并快速使用它。这里发生了什么,我还有什么其他选择?我可以使用具有大缓冲区的文件流将此文件加载到 TStringList 中吗?如果可以,请提供一个例子。

【问题讨论】:

  • 我只是想知道,你为什么要加载 2000 万行文本?使用TFileStream 可能会有更好的运气。
  • 你有一个例子说明如何使用TFileStream将文本文件的行读入TStringList吗?
  • 我希望将文件的行存储在数据库的表中。然后操作将比使用 T*List 后代快得多。那么问题是您打算如何处理这些数据?
  • 简单地说,真正的解决方案是停止尝试将整个文件加载到内存中。

标签: delphi delphi-xe2 filestream readfile tstringlist


【解决方案1】:

当 Delphi 在 Delphi 2009 中切换到 Unicode 时,TStrings.LoadFromStream() 方法(TStrings.LoadFromFile() 在内部调用)对于大型流/文件变得非常效率低下。

在内部,LoadFromStream()整个文件作为TBytes 读入内存,然后使用TEncoding.GetString() 将其转换为UnicodeString(将字节解码为TCharArray,将其复制到最终的UnicodeString,然后释放数组),然后解析UnicodeString(而TBytes 仍在内存中)根据需要将子字符串添加到列表中。

所以,就在LoadFromStream() 退出之前,内存中有四个 文件数据副本——三个副本占用了更糟糕的filesize * 3 字节内存(每个副本都使用它的自己的连续内存块 + 一些 MemoryMgr 开销),以及已解析子字符串的一份副本!当然,当LoadFromStream() 实际退出时,前三个副本被释放。但这解释了为什么在到达该点之前出现内存错误 - LoadFromStream() 正在尝试使用 3-4 GB 的内存来加载 1GB 的文件,而 RTL 的内存管理器无法处理。

如果要将大文件的内容加载到TStringList,最好使用TStreamReader 而不是LoadFromFile()TStreamReader 使用缓冲文件 I/O 方法以小块读取文件。只需在循环中调用它的ReadLine() 方法,Add()'ing 每一行到TStringList。例如:

//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;

也许有一天,LoadFromStream() 可能会被重写为在内部像这样使用TStreamReader

【讨论】:

  • 如果你知道有多少行使用sl.Capacity := KnownValue;来防止多次调用ReallocMem()
  • TStringList 不会在每个 Add() 上调用 ReallocMem(),它会以指数容量增长其内存。
  • 即使您不确切知道有多少列表项/将会有多少个列表项,也可以通过将容量预先设置为具有代表性的大数字来获得巨大的性能提升(最好的猜测,如果可以的话),然后在物品完成加载后将其设置为实际计数以回收任何“废物”。在这种情况下,考虑到文件中每一行的格式是已知的(3 个制表符分隔符加倍),可以很好地估计所需容量:容量:= 文件大小/平均行长度
  • @Trojanian:是的,这将是要使用的构造函数。 DetectBOM 告诉读者是否可以查看文件的开头,看看是否有BOM 指定数据的编码。否则,您必须在 Encoding 参数中指定编码。由于您正在加载一个文本文件,并且TStreamReader(和TStringList)对 Unicode 字符串进行操作,因此读者需要知道文件编码是什么,以便在阅读时将文本解码为 Unicode。
  • @Trojanian: Deltics 告诉你如何预设容量:capacity := file size / average line length。例如:MyStringList.Capacity := Reader.BaseStream.Size div AverageLineLength; 您必须根据数据的实际情况为AverageLineLength 提供一个值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多