Delphi ZLib 压缩/解压缩

3
我在使用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文件,但它确实可以解压缩回原始文件,我可以打开并编辑)。
我也可以使用TZGZipCompression类很好地压缩为.gz文件(有趣的是,我可以完美地打开这个gzip文件)。
然而我的问题是它无法从.gz文件解压缩,并且一旦它到达就会抛出错误。
LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)

有趣的是,帮助文件中的示例如下:
LOutputStream.CopyFrom(LDecompressedStream, 0)

但是这也不起作用。 有人能发现问题吗?
1个回答

6
您的 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;

1
谢谢你的回复,Remy。 然而,我得到了与上次相同的结果。 我可以将文件压缩为zip格式(但无法打开它),并将文件解压缩回原始格式并成功打开。 我可以将文件压缩为gz格式,并打开文件以查看其中的文件,但无法解压缩。每次调用 LOutputStream.CopyFrom(LDecompressedStream, 0); 都会出现“数据错误”的错误提示。 - mikelittlewood
顺便提一下,在我的 Delphi 版本中,容量似乎是一个受保护的属性,所以我只需将最后一部分替换为不检查容量并始终执行此操作 Copy(LOutputStream.Bytes, 0, LOutputStream.Size)。 - mikelittlewood
我解决了,我忘记在解压缩创建流时添加WindowsBits参数。它不知道应该进行gz解压缩,因此默认返回zip格式。 - mikelittlewood
LDecompressedStream := TZDecompressionStream.Create(LContentStream, GetWindowsBits);LDecompressedStream:= TZDecompressionStream.Create(LContentStream,GetWindowsBits); - mikelittlewood
1
@mikelittlewood 谢谢。我已经更新了我的回答中的代码。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接