Delphi ZLib 压缩/解压缩

Delphi ZLib Compress / Decompress

我在 Delphi

中使用 ZLib 单元解压缩时遇到了一个小问题
unit uZCompression;

interface

uses
  uCompression;

type
  TZZipCompression = class(TInterfacedObject, ICompression)
  public
    function DoCompression(aContent: TArray<Byte>): TArray<Byte>;
    function DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
    function GetWindowsBits: Integer; virtual;
  end;

  TZGZipCompression = class(TZZipCompression)
    function GetWindowsBits: Integer; override;
  end;

implementation

uses
  System.ZLib, System.Classes, uMxKxUtils;

{ TZCompression }

function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TMemoryStream;
  LCompressedStream: TZCompressionStream;
begin
  LContentStream := ByteArrayToStream(aContent);

  LOutputStream := TMemoryStream.Create;

  LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
  LCompressedStream.CopyFrom(LContentStream, LContentStream.Size);
  LCompressedStream.Free;

  Result := StreamToByteArray(LOutputStream);

  LOutputStream.Free;
  LContentStream.Free;
end;

function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TMemoryStream;
  LDecompressedStream: TZDecompressionStream;
begin
  LContentStream := ByteArrayToStream(aContent);

  LOutputStream := TMemoryStream.Create;

  LDecompressedStream := TZDecompressionStream.Create(LContentStream);
  LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size);
  LDecompressedStream.Free;

  Result := StreamToByteArray(LOutputStream);

  LOutputStream.Free;
  LContentStream.Free;
end;

function TZZipCompression.GetWindowsBits: Integer;
begin
  Result := 15;
end;

{ TZGZipCompression }

function TZGZipCompression.GetWindowsBits: Integer;
begin
  Result := inherited;
  Result := Result + 16;
end;

end.

这是我的单元,由接口驱动(您不需要知道),数据通过 TArray 变量传入和传出。

我已经对其进行了编码,使其能够进行 2 种类型的压缩,标准 zip 和 gzip,这由传递给函数的 windowsbits 决定。

这里有几个其他函数用于将 TArray 转换为 TMemoryStream

function ByteArrayToStream(aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  Result.Write(aContent, length(aContent)*SizeOf(aContent[0]));
  Result.Position := 0;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
var
  LStreamPos: Int64;
begin
  if Assigned(aStream) then
  begin
    LStreamPos := aStream.Position;
    aStream.Position := 0;
    SetLength(Result, aStream.Size);
    aStream.Read(Result, aStream.Size);
    aStream.Position := LStreamPos;
  end
  else
    SetLength(Result, 0);
end;

现在我可以使用 TZZipCompression class 压缩和解压缩到 .zip 非常好(它不会作为 zip 文件打开,但它确实解压回原始文件,我可以打开和编辑)。

我也可以使用 TZGZipCompression class 压缩到 .gz 也很好(有趣的是我可以很好地打开这个 gzip 文件)。

但是我的问题是它不会从 .gz 文件中解压回来,一旦它命中就抛出错误

LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)

有趣的是,帮助文件示例如下所示

LOutputStream.CopyFrom(LDecompressedStream, 0)

但这也不行。

任何人都可以发现问题吗?

您在 TArray<Byte>TMemoryStream 之间的转换函数是错误的,因为您没有正确访问数组内容。 TArray 是一个动态数组。当调用 TMemoryStream.Write()TMemoryStream.Read() 时,您传递的是 TArray 本身的内存地址,而不是 TArray 指向的数据的内存地址。您需要引用 TArray 以获得正确的内存地址,例如:

function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  try
    if Length(aContent) > 0 then
      Result.WriteBuffer(aContent[0], Length(aContent));
    Result.Position := 0;
  except
    Result.Free;
    raise;
  end;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
  if Assigned(aStream) then
  begin
    SetLength(Result, aStream.Size);
    if Length(Result) > 0 then
      Move(aStream.Memory^, Result[0], aStream.Size);
  end
  else
    SetLength(Result, 0);
end;

或者:

function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  try
    Result.WriteBuffer(PByte(aContent)^, Length(aContent));
    Result.Position := 0;
  except
    Result.Free;
    raise;
  end;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
  if Assigned(aStream) then
  begin
    SetLength(Result, aStream.Size);
    Move(aStream.Memory^, PByte(Result)^, aStream.Size);
  end
  else
    SetLength(Result, 0);
end;

也就是说,您不需要浪费内存来使用 TMemoryStream 复制数组数据。您可以改用 TBytesStream (因为动态数组是引用计数的),例如:

function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TBytesStream;
  LCompressedStream: TZCompressionStream;
begin
  LContentStream := TBytesStream.Create(aContent);
  try
    LOutputStream := TBytesStream.Create(nil);
    try    
      LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
      try
        LCompressedStream.CopyFrom(LContentStream, 0);
      finally
        LCompressedStream.Free;
      end;
      Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
    finally
      LOutputStream.Free;
    end;
  finally
    LContentStream.Free;
  end;
end;

function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TBytesStream;
  LDecompressedStream: TZDecompressionStream;
begin
  LContentStream := TBytesStream.Create(aContent);
  try    
    LOutputStream := TBytesStream.Create(nil);
    try
      LDecompressedStream := TZDecompressionStream.Create(LContentStream, GetWindowsBits);
      try
        LOutputStream.CopyFrom(LDecompressedStream, 0);
      finally
        LDecompressedStream.Free;
      end;
      Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
    finally
      LOutputStream.Free;
    end;
  finally
    LContentStream.Free;
  end;
end;