Delphi ZLib 压缩/解压

mik*_*ood 2 delphi zlib delphi-10.1-berlin

我在 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.
Run Code Online (Sandbox Code Playgroud)

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

我已经将它编码为能够进行两种类型的压缩,标准 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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)
Run Code Online (Sandbox Code Playgroud)

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

LOutputStream.CopyFrom(LDecompressedStream, 0)
Run Code Online (Sandbox Code Playgroud)

但这也行不通。

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

Rem*_*eau 5

您在TArray<Byte>和之间的转换函数TMemoryStream是错误的,因为您没有正确访问数组内容。 TArray是一个动态数组。调用TMemoryStream.Write()and 时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;
Run Code Online (Sandbox Code Playgroud)

或者:

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;
Run Code Online (Sandbox Code Playgroud)

话虽如此,您无需浪费内存使用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;
Run Code Online (Sandbox Code Playgroud)