下载大文件时 Indy TIdFTP“内存不足”错误

问题描述

我正在使用 TIdFTP (Indy 10.6) 上传超过 1GB 的大文件

当我想下载该文件时,我收到一条错误消息,指出“内存不足”。

这是我的代码

var
  FTPfile: TMemoryStream;
begin
  ...
  FTPFile := TMemoryStream.Create;
  ...
  TRY
    IdFTP1.Get ('Myfile.pdf',FTPfile,false);
    FTPfile.Position := 0;
  EXCEPT
    On E: Exception do
      ShowMessage (E.Message);
  END;

我使用的是 32 位版本的 Windows。

是否有解决此问题的解决方案?

解决方法

您正在尝试将 1GB 文件下载到 TMemoryStream。除了一般来说只是一个坏主意之外,这样做的主要问题是 FTP 协议不报告正在传输的文件的大小,因此 TIdFTP.Get() 无法预先分配 {{1 }} 的内部缓冲区。因此,随着文件下载,TMemoryStream 将不得不增长很多次,每次它都会分配一个全新的缓冲区,将现有数据复制到其中,并释放旧缓冲区。这意味着在增长操作期间,内存中存在 2 个缓冲区的时间窗口很小。因此,您实际上使用的内存比您想象的要多得多。在内存中存储 1GB 的文件确实突破了 32 位进程甚至可以分配多少内存的限制。

最终,TMemoryStream.Capacity 会变得如此之大,以至于系统无法再分配第二个缓冲区,从而因“内存不足”错误而失败。

您可以尝试使用 TMemoryStreamTIdFTP.Size() 提前确定远程文件的大小,相应地设置 TIdFTP.List(),然后调用 TMemoryStream.Capacity。这样,只执行了 1 次内存分配。

此外,请确保在使用完 TIdFTP.Get()Free(),否则您将泄漏它能够分配的任何内存。

但实际上,您应该将文件下载到磁盘。您可以为此使用 TMemoryStream,但 TFileStream 有一个重载,它将本地文件路径而不是 TIdFTP.Get() 作为目标。

如果您需要访问内存中的文件数据,您可以在下载完成后根据需要对其进行读取/内存映射。