2013-02-10 2 views
1

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

Код делает несколько вызовов 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 } 
+2

Можете ли вы указать свою версию Delphi? –

+0

Извините, это XE3. Спасибо, Крис. – user2058600

ответ

6

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

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; 

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

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

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

+0

Спасибо, Дэвид, за быстрый ответ. Я попробую ваше решение. В то же время, есть ли у вас какие-либо идеи, почему повторные вызовы TFileStream.Read приведут к переполнению памяти? – user2058600

+3

Скорее всего, вы страдаете от фрагментации адресного пространства. Не знаю, почему. Моя версия использует буфер, выделенный стеком, поэтому он не может страдать от фрагментации. –

+0

Еще раз спасибо, Дэвид. – user2058600