2011-01-05 10 views
8

Necesito comparar si dos TStream descendiente tienen el mismo contenido. El único resultado interesante para mí es el booleano Sí/No.Función Delphi que compara el contenido de dos TStream?

voy a codificar un simple byte de comprobación de bucle después de bytes del contenido de las corrientes.

Pero estoy curioso para saber si hay una función ya existente. No he encontrado ninguna biblioteca dentro de DelphiXE o JCL/JVCL.

¡Por supuesto, las dos secuencias tienen el mismo tamaño!

Respuesta

12

Exactamente, como dijo Nickolay O. usted debe leer su secuencia en bloques y usar CompareMem. Aquí hay un ejemplo (incluyendo prueba de tamaño) ...

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

Tal vez sea útil cuando establece ambas posiciones de flujo en 0 antes de comenzar su ciclo. –

+1

O cargue las secuencias enteras en 2 secuencias de memoria y alimente aquellas para compararmem. – Remko

+0

@Uwe Raabe - +1, tienes toda la razón, @Remko - +1, probablemente también estés en lo cierto, espero que CompareMem salga inmediatamente cuando encuentre la diferencia de bytes –

5

No hay tal función incorporada. Solo puedo recomendar una cosa: leer byte a byte, pero usar bloques de 16-64kbytes sería mucho más rápido.

+1

sí, grandes bloques con llamadas a CompareMem recibirá este hecho –

+0

bien, THX para esta primera respuesta y los consejos sobre grandes bloques. Aceptaré la respuesta de daemon_x ya que está el código completo con el uso de CompareMem(). – TridenT

6

La función IsIdenticalStreams Publicado por daemon_x es excelente - pero necesita un ajuste para que funcione correctamente. (Uwe Raabe ya entendió el problema). Es crítico que restablezca las posiciones de flujo antes de iniciar el bucle - o este procedimiento probablemente arrojará un VERDADERO incorrecto si las dos corrientes ya se habían accedido fuera de esta función.

Esta es la solución final que funciona siempre. Acabo de renombrar la función para adaptarla a mis convenciones de nombres. Gracias daemon_x por la solución elegante.

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; 
+0

thx para agregar comentarios incluso cuando se aprobó la solución. – TridenT

4

Respuestas de user532231 y Mike están trabajando en el 99% de los casos, pero hay comprobaciones adicionales que se harán!

Descendientes de TStream pueden ser casi cualquier cosa, por lo no está garantizado que Stream.Read volverá misma cantidad de datos, incluso si las corrientes son de la misma longitud (descendiente corriente también puede descargar los datos, por lo que pueden volver readed = 0 bytes, mientras espera el siguiente fragmento). Las transmisiones también pueden estar en medios totalmente diferentes y el error de lectura de la secuencia puede ocurrir en una sola.

Para obtener un código de funcionamiento del 100%, todas estas comprobaciones deben realizarse. Modifiqué la función de Mike.

Si esta función se utiliza, por ejemplo, para reescribir la secuencia 2 si no es idéntica a la secuencia 1, se deben verificar todos los errores. Cuando el resultado de la función es Verdadero, todo está bien, pero si es Falso, sería muy inteligente verificar si las Corrientes son realmente diferentes o si solo se produjo algún error.

Editado: añadido algunas comprobaciones adicionales, FilesAreIdentical función basada en StreamsAreIdentical y ejemplo de uso.

// 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; 
+0

+1 por la observación interesante. Gracias por responder una pregunta bastante antigua, todavía estoy sorprendido de ver cómo las personas [de software] son ​​tan dedicadas a compartir y mejorar e intercambiar continuamente ... Nunca he visto esto en otro lado :) – TridenT

+0

TridenT: Simplemente haciendo del mundo un mejor lugar;) – david

+0

Definitivamente dejaría que se lanzara la excepción. Si algo sale mal durante la lectura y "comerá ese problema" y devolverá solo un booleano, nadie podrá determinar cuál fue el problema. Y no es necesario comparar 'aBlockSize' de la memoria, sino simplemente' Buffer1Readed' o 'Buffer2Readed' (serán iguales). – TLama

Cuestiones relacionadas