Delphi ZLib压缩/解压缩

时间:2016-12-06 17:34:27

标签: 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.

这是我的单位,由一个界面驱动(你不需要知道),数据通过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;

现在我可以使用TZZipCompression类完全压缩和解压缩到.zip(它不会像zip文件一样打开,但它会解压缩回我可以打开和编辑的原始文件)。 / p>

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

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

LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)

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

LOutputStream.CopyFrom(LDecompressedStream, 0)

但这也不起作用。

有人能发现这个问题吗?

1 个答案:

答案 0 :(得分:2)

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;