В Delphi, в том числе в последних версиях (могу проверить только до 10.1 Berlin, хотя негативные отзывы есть и о 10.2 Tokyo), класс TMemoryStream не может выделить под данные более 2 Гб даже при компиляции в 64-битном режиме. Попытки записать в поток большие объёмы приводят к ошибке "Out of memory while expanding memory stream".
Можно ли решить эту проблему без создания собственного класса, реализующего поток в памяти?
Ответ
Давайте взглянем в определение класса TMemoryStream в файле System.Classes.pas:
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
function Realloc(var NewCapacity: Longint): Pointer; virtual;
property Capacity: Longint read FCapacity write SetCapacity;
public
destructor Destroy; override;
procedure Clear;
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SetSize(const NewSize: Int64); override;
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
end;
Сразу отмечаем, что для адресации памяти используются переменные типа longint, что, естественно, объясняет невозможность использования более 2 Гб. Более того, если взглянуть на методы Read (их два) непосредственного предка TMemoryStream - TCustomMemoryStream
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
Result := FSize - FPosition;
if Result > 0 then
begin
if Result > Count then Result := Count;
Move((PByte(FMemory) + FPosition)^, Buffer, Result);
Inc(FPosition, Result);
Exit;
end;
end;
Result := 0;
end;
function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
Result := FSize - FPosition;
if Result > 0 then
begin
if Result > Count then Result := Count;
Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result);
Inc(FPosition, Result);
Exit;
end;
end;
Result := 0;
end;
, то заметим, что результат может быть посчитан неправильно.
Поэтому решением было: заменить тип переменных внутри TMemoryStream на NativeInt и Int64, в том числе локальных переменных, а методы TCustomMemoryStream.Read переписать, например, так:
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint;
var
diff: Int64;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
diff := FSize - FPosition;
if diff > 0 then
begin
if diff > Count then
Result := Count
else
Result := diff;
Move((PByte(FMemory) + FPosition)^, Buffer, Result);
Inc(FPosition, Result);
Exit;
end;
end;
Result := 0;
end;
function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint;
var
diff: Int64;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
diff := FSize - FPosition;
if diff > 0 then
begin
if diff > Count then
Result := Count
else Result := diff;
Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result);
Inc(FPosition, Result);
Exit;
end;
end;
Result := 0;
end;
Это позволяет работать с объёмами памяти более 2 Гб (тестовый пример спокойно обработал 5-гигабайтный файл, полностью скопировав его в поток.
Для тех, кто захочет внести изменения у себя в System.Classes, напомню, что предварительно необходимо сохранить резервную копию этого файла - на всякий случай :)
Update 1
Для тех, кто по какой-то причине не хочет идти по предложенным выше вариантам, могу предложить класс TSegmentedMemoryStream - он доступен для скачивания и работает (проверено!).
Комментариев нет:
Отправить комментарий