比较两个TStream中的内容的Delphi函数?

9
我需要比较两个TStream类的子类是否具有相同的内容。对我来说,唯一重要的结果是布尔型的“是”或“否”。
我将编写一个简单的循环,逐字节检查流的内容。
不过,我很好奇是否已经存在这样的函数。我没有在DelphiXE或JCL/JVCL库中找到任何相关函数。
当然,这两个流的大小是相同的!
4个回答

11

正如Nickolay O.所说,您应该分块读取流并使用CompareMem。这里有一个示例(包括大小测试)...

function IsIdenticalStreams(Source, Destination: TStream): boolean;
const Block_Size = 4096;

var Buffer_1: array[0..Block_Size-1] of byte;
    Buffer_2: array[0..Block_Size-1] of byte;
    Buffer_Length: integer;

begin
  Result := False;

  if Source.Size <> Destination.Size then
    Exit;

  while Source.Position < Source.Size do
    begin
      Buffer_Length := Source.Read(Buffer_1, Block_Size);
      Destination.Read(Buffer_2, Block_Size);

      if not CompareMem(@Buffer_1, @Buffer_2, Buffer_Length) then
        Exit;
    end;

  Result := True;
end;

1
也许在开始循环之前,将两个流位置都设置为0会有所帮助。 - Uwe Raabe
1
或者将整个流加载到两个MemoryStream中,然后将它们提供给CompareMem进行比较。 - Remko
4
我不喜欢 TMemoryStream,因为它需要一个连续的内存块,所以在处理大文件时会出现问题。也许在 dcc64 中这个问题已经得到解决,但在此之前最好避免使用。如果流的大小小于 Block_Size,那么这段代码将无法正常工作,因为你将会比较随机堆栈噪声。 - David Heffernan
1
@David Heffernan:只要你提前调用SetSize预分配内存,TMemoryStream就可以正常工作。但是,为了比较两个流,最好不要将它们完全加载到内存中,而是在第一个不匹配处开始加载、比较并停止,特别是对于磁盘上的大型流(或其他“慢速”来源,如数据库字段)。 - user160694
1
@Idsandon 不,它并不正常工作!你可以有一个大于2GB的流,这肯定会失败。或者,可能没有足够大的可用连续地址空间块。在大多数正常使用模式下,在达到2GB之前就会发生这种情况。 - David Heffernan
显示剩余6条评论

9

daemon_x发布的IsIdenticalStreams函数非常出色,但需要做出一个调整才能正常工作。(Uwe Raabe已经注意到了这个问题。)在开始循环之前,重置流位置至关重要,否则如果两个流在此函数外被访问,则此过程可能会返回一个不正确的TRUE。

这是每次都有效的最终解决方案。我只是根据自己的命名规范将函数重命名。感谢daemon_x提供的优雅解决方案。

function StreamsAreIdentical(Stream1, Stream2: TStream): boolean;
const
  Block_Size = 4096;

var
  Buffer_1: array[0..Block_Size-1] of byte;
  Buffer_2: array[0..Block_Size-1] of byte;
  Buffer_Length: integer;

begin

  Result := False;

  if Stream1.Size <> Stream2.Size then exit;

  // These two added lines are critical for proper operation 
  Stream1.Position := 0;
  Stream2.Position := 0;

  while Stream1.Position < Stream1.Size do
  begin

    Buffer_Length := Stream1.Read(Buffer_1, Block_Size);
    Stream2.Read(Buffer_2, Block_Size);
    if not CompareMem(@Buffer_1, @Buffer_2, Buffer_Length) then exit;

  end;

  Result := True;

end;

5

没有这样的内置函数。我唯一能推荐的是,不要逐字逐句地阅读,而应该使用16-64 kbytes的块来阅读,这样会更快。


1
没错,使用 CompareMem 函数的大块代码将完成此操作。 - David Heffernan
好的,谢谢你的第一个回答和有关大块的提示。我会接受来自daemon_x的答案,因为它包含了使用CompareMem()的完整代码。 - TridenT

4

user532231Mike的答案在99%的情况下有效,但还需要进行其他检查

TStream的后代几乎可以是任何东西,因此,即使流长度相同(流的后代也可以下载数据,因此可能返回读取=0字节,同时等待下一块),也不能保证Stream.Read会返回相同数量的数据。流也可能完全不同的媒体,流读取错误可能只发生在其中一个上。

为了实现100%的工作代码,应进行所有这些检查。我修改了Mike的函数。

如果例如将该函数用于在Stream1与2不相同时重新编写流,则应检查所有错误。当函数结果为True时,一切正常,但如果结果为False,则非常明智地检查流是否实际上不同或只是发生了某个错误。

编辑:添加了一些额外的检查,基于StreamsAreIdentical的FilesAreIdentical函数以及使用示例。

// Usage example

var lError: Integer;
...
 if FilesAreIdentical(lError, 'file1.ext', 'file2.ext')
    then Memo1.Lines.Append('Files are identical.')
    else case lError of
           0: Memo1.Lines.Append('Files are NOT identical!');
           1: Memo1.Lines.Append('Files opened, stream read exception raised!');
           2: Memo1.Lines.Append('File does not exist!');
           3: Memo1.Lines.Append('File open exception raised!');
         end; // case
...

// StreamAreIdentical

function StreamsAreIdentical(var aError: Integer;
                             const aStream1, aStream2: TStream;
                             const aBlockSize: Integer = 4096): Boolean;

var
  lBuffer1: array of byte;
  lBuffer2: array of byte;
  lBuffer1Readed,
  lBuffer2Readed,
  lBlockSize: integer;

begin
  Result:=False;
  aError:=0;
  try
    if aStream1.Size <> aStream2.Size
       then Exit;

    aStream1.Position:=0;
    aStream2.Position:=0;

    if aBlockSize>0
       then lBlockSize:=aBlockSize
       else lBlockSize:=4096;

    SetLength(lBuffer1, lBlockSize);
    SetLength(lBuffer2, lBlockSize);

    lBuffer1Readed:=1; // just for entering while

    while (lBuffer1Readed > 0) and (aStream1.Position < aStream1.Size) do
    begin
      lBuffer1Readed := aStream1.Read(lBuffer1[0], lBlockSize);
      lBuffer2Readed := aStream2.Read(lBuffer2[0], lBlockSize);

      if (lBuffer1Readed <> lBuffer2Readed) or ((lBuffer1Readed <> lBlockSize) and (aStream1.Position < aStream1.Size))
         then Exit;

      if not CompareMem(@lBuffer1[0], @lBuffer2[0], lBuffer1Readed)
         then Exit;
    end; // while

    Result:=True;
  except
    aError:=1; // stream read exception
  end;
end;


// FilesAreIdentical using function StreamsAreIdentical

function FilesAreIdentical(var aError: Integer;
                           const aFileName1, aFileName2: String;
                           const aBlockSize: Integer = 4096): Boolean;

var lFileStream1,
    lFilestream2: TFileStream;

begin
 Result:=False;
 try
   if not (FileExists(aFileName1) and FileExists(aFileName2))
      then begin
        aError:=2; // file not found
        Exit;
      end;

   lFileStream1:=nil;
   lFileStream2:=nil;
   try
     lFileStream1:=TfileStream.Create(aFileName1, fmOpenRead or fmShareDenyNone);
     lFileStream2:=TFileStream.Create(aFileName2, fmOpenRead or fmShareDenyNone);
     result:=StreamsAreIdentical(aError, lFileStream1, lFileStream2, aBlockSize);
   finally
     if lFileStream2<>nil
        then lFileStream2.Free;

     if lFileStream1<>nil
        then lFileStream1.Free;
   end; // finally
 except
   aError:=3; // file open exception
 end; // except
end;

+1 对于有趣的评论。感谢回答一个相当古老的问题,我仍然很惊讶看到[软件]人们如此热衷于分享、不断改进和交流...我从未在其他地方见过这样的情况 :) - TridenT
你说得对,这是个人口味问题... 无论如何,使用@符号后跟用户名(在您开始输入时会触发自动建议)来通知某人有新评论(例如,@TLama会通知我)。由于您是帖子的所有者,因此我没有使用它,您始终会被通知,以及问题的所有者。感谢您加入Stack Overflow! - TLama
1
@david 这个能在小于 4096 大小的流上工作吗?(Buffer1Readed <> aBlockSize) 发生了什么? - Merlin W.
1
@MerlinW。是的,它可以,甚至可以在大小为0的流上工作。;) 它仅比较实际读取了多少,如果不同,则在 if (Buffer1Readed <> Buffer2Readed) 之前退出,并且此部分 or ((Buffer1Readed <> aBlockSize) and (aStream1.Position < aStream1.Size)) 允许仅在流结束时读取小于 aBlock 大小的数据。这实际上就是你所要求的 ;) - david
1
@MerlinW。今天在处理其他项目时,我发现CompareMem不像Length或Move那样安全,它通常操作零长度动态数组(nil)。如果有人出于某种奇怪的原因选择了0长度的块大小,则函数将崩溃(甚至可能导致无限循环,因为在期望到达流的末尾时读取了0字节)。因此,我添加了更多检查,并使用FilesAreIdentical函数的用法示例,使用StreamsAreIdentical ;) - david
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接