Delphi TFileStream "не хватает памяти"

У меня возникли проблемы с некоторым кодом Delphi, который использует TFileStream для чтения фрагментов данных из файла в динамический массив. Первоначальная цель при написании кода - сравнить содержимое двух файлов, которые имеют одинаковый размер, но, возможно, разные метки даты и времени, чтобы увидеть, совпадает ли содержимое. Это делается путем считывания данных из каждого файла пары в отдельные динамические массивы и сравнения каждого байта одного массива с соответствующим байтом другого.

Код делает несколько вызовов TFileStream.Read. После примерно 75 вызовов программа вылетает с сообщением об ошибке "Недостаточно памяти".

Кажется, не имеет значения, насколько велики блоки данных, которые читаются, похоже, это количество вызовов, которое приводит к сообщению об ошибке.

Код - это написанная мною функция, которая вызывается в другом месте всякий раз, когда программа встречает два файла, которые нужно сравнить (в которых, по причинам, которые я не буду описывать, может быть сорок или пятьдесят различных пар файлов). Ошибка "Недостаточно памяти" возникает, если это один файл, который читается небольшими блоками, или несколько файлов, которые читаются целиком. Похоже, что количество вызовов является определяющим фактором ошибки.

Хотя я понимаю, что могут быть более элегантные способы сравнения файлов, чем показано ниже, я хотел бы знать, что не так с использованием вызовов TFileStream и / или SetLength, которые вызывают проблемы с памятью. Я попытался освободить память после каждого вызова (как показано в коде), и это, кажется, не имеет значения.

Буду благодарен, если кто-нибудь сможет объяснить, что происходит не так.

function Compare_file_contents(SPN,TPN : String; SourceFileSize : int64) : boolean;

var

  SF                : TFileStream; //First file of pair for comparison
  TF                : TFileStream; //Second file of pair
  SourceArray       : TBytes; // Buffer array to receive first file data
  TargetArray       : TBytes; //Buffer array to receive second file data
  ArrayLength       : int64; //Length of dynamic array
  Position          : int64; //Position within files to start each block of data read
  TestPosition      : int64; //Position within dynamic arrays to compare each byte
  MaxArrayLength    : integer; //Maximum size for the buffer arrays
  LastRun           : Boolean; //End first repeat loop

begin

{ The comparison has an arbitrary upper boundary of 100 MB to avoid slowing the
  the overall program. The main files bigger than this will be *.pst files that
  will most likely have new dates every time the program is run, so it will take
  about the same time to copy the files as it does to read and compare them, and
  it will have to be done every time.

  The function terminates when it is confirmed that the files are not the same.
  If the source file is bigger than 100 MB, it is simply assumed that they are
  not identical, thus Result = False. Also, LongInt integers (=integers) have
  a range of -2147483648..2147483647, so files bigger than 2 GB will have
  overflowed to a negative number. Hence the check to see if the file size is
  less than zero.

  The outer repeat ... until loop terminates on LastRun, but LastRun should only
  be set if SecondLastRun is True, because it will skip the final comparisons in
  the inner repeat ... until loop otherwise. }

  Result := True;
  LastRun := False;
  MaxArrayLength := 1024*1024;
  if (SourceFileSize > 100*1024*1024) or (SourceFileSize < 0) then Result := False
    else
      begin

{ The comparison is done by using TFileStream to open and read the data from
  the source and target files as bytes to dynamic arrays (TBytes). Then a repeat
  loop is used to compare individual bytes until a difference is found or all
  of the information has been compared. If a difference is found, Result is
  set to False. }

    if SourceFileSize > MaxArrayLength then ArrayLength := MaxArrayLength
      else ArrayLength := SourceFileSize;
    SF := TFileStream.Create(SPN,fmOpenRead);
    TF := TFileStream.Create(TPN,fmOpenRead);
    Position := 0;
    SetLength(SourceArray,ArrayLength);
    SetLength(TargetArray,ArrayLength);
    try
      SF.Read(SourceArray,ArrayLength);
      TF.Read(TargetArray,ArrayLength);
      Position := SF.Position;
    finally
      SF.Free;
      TF.Free;
    end;
      repeat
      TestPosition := 0;
        repeat
          if SourceArray[TestPosition] <> TargetArray[TestPosition] then
            Result := False;
          Inc(TestPosition);
        until (Result = False) or (TestPosition = ArrayLength);
        if SourceFileSize > Position then
          begin
            if SourceFileSize - Position - MaxArrayLength > 0 then
              ArrayLength := MaxArrayLength
              else ArrayLength := SourceFileSize - Position;
            SF := TFileStream.Create(SPN,fmOpenRead);
            TF := TFileStream.Create(TPN,fmOpenRead);
            SF.Position := Position;
            TF.Position := Position;
            try
              SF.Read(SourceArray,ArrayLength);
              TF.Read(TargetArray,ArrayLength);
              Position := SF.Position;
            finally
              SF.Free;
              TF.Free;
            end;
        end else LastRun := True;
      until (Result = False) or LastRun;
      Finalize(SourceArray);
      Finalize(TargetArray);
  end;
end; { Compare_file_contents }

1 ответ

Эта рутина кажется намного более сложной, чем она должна быть. Вместо того, чтобы пытаться отлаживать его, я предлагаю вам свою процедуру, которая сравнивает потоки.

function StreamsEqual(Stream1, Stream2: TStream): Boolean;
const
  OneKB = 1024;
var
  Buffer1, Buffer2: array [0..4*OneKB-1] of Byte;
  SavePos1, SavePos2: Int64;
  Count: Int64;
  N: Integer;
begin
  if Stream1.Size<>Stream2.Size then begin
    Result := False;
    exit;
  end;

  SavePos1 := Stream1.Position;
  SavePos2 := Stream2.Position;
  Try
    Stream1.Position := 0;
    Stream2.Position := 0;

    Count := Stream1.Size;
    while Count <> 0 do begin
      N := Min(SizeOf(Buffer1), Count);
      Stream1.ReadBuffer(Buffer1, N);
      Stream2.ReadBuffer(Buffer2, N);
      if not CompareMem(@Buffer1, @Buffer2, N) then begin
        Result := False;
        exit;
      end;
      dec(Count, N);
    end;
    Result := True;
  Finally
    Stream1.Position := SavePos1;
    Stream2.Position := SavePos2;
  End;
end;

Если вы хотите добавить проверку размера в 100 МБ к этой функции, очевидно, где и как это сделать.

Приведенная выше процедура использует выделенный стеком буфер. В отличие от вашей версии размещается в куче. Возможно, ваша версия приводит к фрагментации кучи.

Я понимаю, что это не отвечает на прямой вопрос, который вы задали. Тем не менее, это решит вашу проблему. Я надеюсь, что это окажется полезным.

Другие вопросы по тегам