【问题标题】:Delphi ZLib Compress / DecompressDelphi ZLib 压缩/解压缩
【发布时间】:2016-12-06 17:34:27
【问题描述】:

我在使用 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 类完美地压缩和解压缩为 .zip(它不会以 zip 文件的形式打开,但它会解压缩回我可以打开和编辑的原始文件)。

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

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

LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)

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

LOutputStream.CopyFrom(LDecompressedStream, 0)

但这也不起作用。

谁能发现问题?

【问题讨论】:

    标签: delphi zlib delphi-10.1-berlin


    【解决方案1】:

    TArray&lt;Byte&gt;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;
    

    【讨论】:

    • 感谢雷米。但是,我确实得到了与上次相同的结果。我可以将文件压缩为 zip ok(但不能将其作为 zip 文件打开),然后将文件解压缩回原始文件并打开 ok。我可以把文件压缩到gz ok,打开文件可以看到里面的文件,但是不会解压。调用 LOutputStream.CopyFrom(LDecompressedStream, 0); 时不断给出错误“数据错误”;
    • 在旁注中,容量现在在我的 Delphi 版本中似乎是一个受保护的属性,所以我只是替换了最后一位不检查容量并一直这样做 Copy(LOutputStream.Bytes, 0, LOutputStream.Size)
    • 我解决了,我忘记将 WindowsBits 添加到解压创建流中。它不知道它是用来做gz解压的,默认回zip。
    • LDecompressedStream := TZDecompressionStream.Create(LContentStream, GetWindowsBits);
    • @mikelittlewood 谢谢。我已经更新了答案中的代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-01
    相关资源
    最近更新 更多