我使用中发现压缩一切正常,但解压缩时释放建立的实例时即内存越界,不知何故?这是超级猛料中的LZW压缩单元:unit Lzw;interfaceuses
  Windows, SysUtils, Classes;const
  NOCODE = -1; // 空编码
  LZWBITS = 8; // 字对处理位
  LZWBUFFER = $FFFF; // 编码处理缓存容量(输入缓存容量。经实践,该值能达到较好的效率)
  LZWMAXBITS = 12; // 最大的编码位(增加该值会增加编码表的内存空间)
  LZWSTACKBUFFERSIZE = $FFFF; // 栈缓存容量(要保证它足够大)
  LZWEXPORTBLOCKSIZE = $FFFF; // 输出缓存容量
  LZWMAXCODES = 1 shl LZWMAXBITS; // 最大编码(4096)
  LZWTABLESIZE = 1 shl (LZWBITS + LZWMAXBITS); // 编码表容量(2MB空间)type
  TLZWEncode = class(TObject)
  private
    EncodeTable: array [0..LZWTABLESIZE - 1] of Word; // 编码表
    EncodePointer: array [0..LZWMAXCODES - 1] of LongWord; // 经过编码的缓存
    ExportBlock: Pointer; // 存放编码后的数据指针(输出缓存块指针)
    ExportBlockPtr: array of Byte; // 该指针指向 ExportBlock ,用于访问数组
    InitBits: Integer; // 压缩数据的起始位数
    ClearCode: Integer; // 清除码
    EofCode: Integer; // 结束码
    PrefixCode: Integer; // 字头码
    SuffixCode: Integer; // 字尾码
    Encode: Integer; // 压缩编码
    RunBits: Integer; // 当前处理位
    MaxCodeSize: Integer; // 当前处理最大编码
    FBegin: Boolean; // 开始处理标志
    FExportSize: Integer; // 输出数据块大小
    FExportIndex: Integer; // 输出数据块索引
    FExportTotalSize: Integer; // 记录输出缓存块大小
    ShiftBits: Integer; // 用于位处理,作临时位
    ShiftCode: Integer; // 用于位处理,作临时代码
  protected
    procedure ExportData(AData: Integer); virtual; // 输出数据(虚方法)
  public
    function GetExportPointer: Pointer; // 返回输出指针
    function GetExportSize: Integer; // 返回输出大小
    procedure GetBegin; // 置开始编码标志
    procedure GetEnd; // 置结束编码标志
    procedure Execute(Data: array of Byte; DataSize: Integer); virtual; // 执行编码过程(虚方法)
    constructor Create;
    destructor Destroy; override;
  end;  TLZWUnencode = class(TObject)
  private
    InitBits: Integer; // 压缩数据的起始位数
    ClearCode: Integer; // 清除码
    EofCode: Integer; // 结束码
    PrefixCode: Integer; // 字头码
    SuffixCode: Integer; // 字尾码
    Encode: Integer; // 压缩编码
    RunBits: Integer; // 当前处理位
    MaxCodeSize: Integer; // 当前处理最大编码
    ExportBlock: Pointer; // 存放编码后的数据指针(输出缓存块指针)
    ExportBlockPtr: array of Byte; // 该指针指向 ExportBlock ,用于访问数组
    StackIndex: Integer; // 栈索引
    StackTable: array [0..LZWSTACKBUFFERSIZE - 1] of Byte; // 栈表
    PrefixTable: array [0..LZWMAXCODES - 1] of Word; // 字头表
    SuffixTable: array [0..LZWMAXCODES - 1] of Byte; // 字尾表
    FExportSize: Integer; // 输出数据块大小
    FExportIndex: Integer; // 输出数据块索引
    FExportTotalSize: Integer; // 记录输出缓存块大小
    ShiftBits: Integer; // 用于位处理,作临时位
    ShiftCode: Integer; // 用于位处理,作临时代码
  protected
    procedure ExportData(AData: Integer); virtual; // 输出数据(虚方法)
  public
    function GetExportPointer: Pointer; // 返回输出指针
    function GetExportSize: Integer; // 返回输出大小
    procedure GetBegin; // 开始解码(分配输出内存空间)
    procedure GetEnd; // 结束解码(释放输出内存空间)
    procedure Execute(Data: array of Byte; DataSize: Integer); virtual; // 执行解码过程(虚方法)
    constructor Create;
    destructor Destroy; override;
  end;implementation{ TLZWEncode }constructor TLZWEncode.Create;
begin
  InitBits := LZWBITS;
  ClearCode := 1 shl InitBits;
  EofCode := ClearCode + 1;
  Encode := EofCode + 1;
  RunBits := InitBits + 1;
  MaxCodeSize := 1 shl RunBits;
  FBegin := False;
  FExportSize := 0;
  FExportIndex := 0;
  FExportTotalSize := 0;
  ShiftBits := 0;
  ShiftCode := 0;
end;destructor TLZWEncode.Destroy;
begin
  FreeMem(ExportBlock);
  inherited;
end;procedure TLZWEncode.Execute(Data: array of Byte; DataSize: Integer);
var
  AIndex: Integer;
  ArrayIndex: Integer;
  Vi: Integer;
begin
  AIndex := 0;
  FExportIndex := 0;
  FExportTotalSize := LZWEXPORTBLOCKSIZE;
  { 处理文件首字节,赋值给字头码 }
  if FBegin then
  begin
    FBegin := False;
    ExportData(ClearCode);
    PrefixCode := Data[AIndex];
    Inc(AIndex);
  end;
  { 编码过程 }
  while AIndex < DataSize do
  begin
    { 取出数据,赋值给字尾码 }
    SuffixCode := Data[AIndex];
    Inc(AIndex);
    { 构造地址 }
    ArrayIndex := (PrefixCode shl LZWBITS) + SuffixCode;
    { 无可编码字对的情况 }
    if EncodeTable[ArrayIndex] = 0 then
    begin
      ExportData(PrefixCode); // 输出字头
      { 当前编码等于最大编码值的情况,作初始化工作 }
      if Encode = LZWMAXCODES then
      begin
        ExportData(ClearCode); // 输出清除码
        Encode := EofCode + 1;
        RunBits := InitBits + 1;
        MaxCodeSize := 1 shl RunBits;
        { 只需初始化编码过的内存区 }
        for Vi := Encode to LZWMAXCODES - 1 do
          EncodeTable[EncodePointer[Vi]] := 0;
      end
      else begin
        { 当前编码等于最大处理编码的情况 }
        if Encode = MaxCodeSize then
        begin
          Inc(RunBits); // 当前处理位增加
          MaxCodeSize := 1 shl RunBits; // 相应最大编码增加
        end;
        EncodeTable[ArrayIndex] := Encode; // 加入编码表
        EncodePointer[Encode] := ArrayIndex;
        Inc(Encode);
      end;
      PrefixCode := SuffixCode;
    end
    { 编码可匹配的情况 }
    else begin
      PrefixCode := EncodeTable[ArrayIndex];
    end;
  end;
end;procedure TLZWEncode.ExportData(AData: Integer);
{ 输出过程 }
  procedure ExportProcedure;
  begin
    while ShiftBits >= LZWBITS do
    begin
      ExportBlockPtr[FExportIndex] := ShiftCode and $00FF;
      Inc(FExportIndex);
      if FExportIndex = FExportTotalSize then
      begin
        { 重新分配内存后首地址可能改变 }
        ReallocMem(ExportBlock, FExportIndex + LZWEXPORTBLOCKSIZE);
        Pointer(ExportBlockPtr) := ExportBlock;
        Inc(FExportTotalSize, LZWEXPORTBLOCKSIZE);
      end;
      ShiftCode := ShiftCode shr LZWBITS;
      Dec(ShiftBits, LZWBITS);
    end;
  end;
begin
  { 输出位总是大于 LZWBITS 的 }
  ShiftCode := AData shl ShiftBits + ShiftCode;
  Inc(ShiftBits, RunBits);
  ExportProcedure;
end;function TLZWEncode.GetExportPointer: Pointer;
begin
  Result := ExportBlock;
end;function TLZWEncode.GetExportSize: Integer;
begin
  FExportSize := FExportIndex;
  Result := FExportSize;
end;

解决方案 »

  1.   


    procedure TLZWEncode.GetBegin;
    begin
      FBegin := True;
      { 有可能输出缓存大于输入缓存,如果发生,到时再重新分配内存 }
      ExportBlock := AllocMem(LZWEXPORTBLOCKSIZE);
      Pointer(ExportBlockPtr) := ExportBlock;
    end;procedure TLZWEncode.GetEnd;
    begin
      ExportData(PrefixCode);
      EXportData(EofCode);
      { 最后的处理是看看有没有没处理的位 }
      while ShiftBits > 0 do
      begin
        ExportBlockPtr[FExportIndex] := ShiftCode and $00FF;
        Inc(FExportIndex);
        if FExportIndex = FExportTotalSize then
        begin
          ReallocMem(ExportBlock, FExportIndex + LZWEXPORTBLOCKSIZE);
          Pointer(ExportBlockPtr) := ExportBlock;
          Inc(FExportTotalSize, LZWEXPORTBLOCKSIZE);
        end;
        ShiftCode := ShiftCode shr LZWBITS;
        Dec(ShiftBits, LZWBITS);
      end;
    end;{ TLZWUnencode }constructor TLZWUnencode.Create;
    begin
      InitBits := LZWBITS;
      ClearCode := 1 shl InitBits;
      EofCode := ClearCode + 1;
      Encode := EofCode + 1;
      RunBits := InitBits + 1;
      MaxCodeSize := 1 shl RunBits;
      ShiftBits := 0;
      ShiftCode := 0;
      FExportSize := 0;
      FExportIndex := 0;
      FExportTotalSize := 0;
    end;destructor TLZWUnencode.Destroy;
    begin
      inherited;
    end;procedure TLZWUnencode.Execute(Data: array of Byte; DataSize: Integer);
    const
      MaskCode: array [0..LZWMAXBITS] of Word = (
        $0000, $0001, $0003, $0007,
        $000F, $001F, $003F, $007F,
        $00FF, $01FF, $03FF, $07FF,
        $0FFF);
    var
      AIndex: Integer;
      CurrentCode, ACode: Integer;
    begin
      AIndex := 0;
      FExportIndex := 0;
      FExportTotalSize := LZWSTACKBUFFERSIZE;
      { 解码过程 }
      while AIndex < DataSize do
      begin
        { 取出数据 }
        while (ShiftBits < RunBits) and (AIndex < DataSize) do
        begin
          ShiftCode := Data[AIndex] shl ShiftBits + ShiftCode;
          Inc(AIndex);
          Inc(ShiftBits, LZWBITS);
        end;    if AIndex >= DataSize then
          Exit;
        CurrentCode := ShiftCode and MaskCode[RunBits];
        ShiftCode := ShiftCode shr RunBits;
        Dec(ShiftBits, RunBits);
        { 遇到结束码则退出 }
        if CurrentCode = EofCode then
          Exit;
        { 遇到清除码则初始化 }
        if CurrentCode = ClearCode then
        begin
          RunBits := InitBits + 1;
          Encode := EofCode + 1;
          MaxCodeSize := 1 shl RunBits;
          PrefixCode := NOCODE;
          SuffixCode := NOCODE;
        end
        else
        begin
          ACode := CurrentCode;
          StackIndex := 0;
          { 当前代码正好与当前编码值相等的情况 }
          if ACode = Encode then
          begin
            StackTable[StackIndex] := SuffixCode;
            Inc(StackIndex);
            ACode := PrefixCode;
          end;
          { 当前代码大于当前编码值的情况,递归取值 }
          while ACode > EofCode do
          begin
            StackTable[StackIndex] := SuffixTable[ACode];
            Inc(StackIndex);
            ACode := PrefixTable[ACode];
          end;
          SuffixCode := ACode;
          { 输出数据 }
          ExportData(ACode);
          while StackIndex > 0 do
          begin
            Dec(StackIndex);
            ExportData(StackTable[StackIndex]);
          end;
          { 加入字典 }
          if (Encode < LZWMAXCODES) and (PrefixCode <> NOCODE) then
          begin
            PrefixTable[Encode] := PrefixCode;
            SuffixTable[Encode] := SuffixCode;
            Inc(Encode);
            if (Encode >= MaxCodeSize) and (RunBits < LZWMAXBITS) then
            begin
              MaxCodeSize := MaxCodeSize shl 1;
              Inc(RunBits);
            end;
          end;
          PrefixCode := CurrentCode;
        end;
      end;
    end;procedure TLZWUnencode.ExportData(AData: Integer);
    begin
      ExportBlockPtr[FExportIndex] := AData;
      Inc(FExportIndex);
      if FExportIndex = FExportTotalSize then
      begin
        ReallocMem(ExportBlock, FExportIndex + LZWSTACKBUFFERSIZE);
        Pointer(ExportBlockPtr) := ExportBlock;
        Inc(FExportTotalSize, LZWSTACKBUFFERSIZE);
      end;
    end;procedure TLZWUnencode.GetBegin;
    begin
      ExportBlock := AllocMem(LZWSTACKBUFFERSIZE);
      Pointer(ExportBlockPtr) := ExportBlock;
    end;procedure TLZWUnencode.GetEnd;
    begin
      FreeMem(ExportBlock);
    end;function TLZWUnencode.GetExportPointer: Pointer;
    begin
      Result := ExportBlock;
    end;function TLZWUnencode.GetExportSize: Integer;
    begin
      FExportSize := FExportIndex;
      Result := FExportSize;
    end;end. =================================================================我的代码:
     
    procedure TForm1.Button1Click(Sender: TObject); //压缩过程,正常 
    var
      L: TLZWEncode;
      Ms: TMemoryStream;
      Buf: array of Byte;
      Fs: TFileStream;
    begin
      Ms := TMemoryStream.Create;
      Ms.LoadFromFile('C:\Codetest\Dictionary.txt');
      SetLength(Buf, Ms.Size);
      Ms.Read(Buf[0], Ms.Size);
      L := TLZWEncode.Create;
      L.GetBegin;
      L.Execute(Buf, Ms.Size);
      L.GetEnd;
      Fs := TFileStream.Create('C:\Codetest\Dictionary.lzw', fmCreate);
      Fs.Write(L.GetExportPointer^, L.GetExportSize);
      FreeAndNil(Fs);
      FreeAndNil(L);
      FreeAndNil(Ms);
    end;procedure TForm1.Button2Click(Sender: TObject); //解压缩过程 
    var
      L: TLZWUnencode;
      Ms: TMemoryStream;
      Buf: array of Byte;
      Fs: TFileStream;
    begin
      Ms := TMemoryStream.Create;
      Ms.LoadFromFile('C:\Codetest\Dictionary.lzw');
      SetLength(Buf, Ms.Size);
      Ms.Read(Buf[0], Ms.Size);
      L := TLZWUnencode.Create;
      L.GetBegin;
      L.Execute(Buf, Ms.Size);
      Fs := TFileStream.Create('C:\Codetest\Dict.txt', fmCreate);
      Fs.Write(L.GetExportPointer^, L.GetExportSize);
      L.GetEnd;
      FreeAndNil(Fs);
      FreeAndNil(L);  // 只要一释放,立即出错! 
      FreeAndNil(Ms);
    end;我调试了几个小时也没有发现哪里出错,而这个等着要用,望列位高人救我于水深火热之中!感谢!!
      

  2.   

    兄弟,不是我不帮你,我现在忙得要死。帮你顶吧。偷闲上来看看~ 对了,猛料有问题去eping那里直接问kingron,http://www.eping.net/fourm/list.asp?boardid=8
      

  3.   

    我不会 ^^! 哎~~ 说来汗呀,我压缩一直都用VCLZIP
      

  4.   

    destructor TLZWUnencode.Destroy;
    begin
      inherited;
    end;
    这个有什么意义呢?不要override这个试试,不好意思喔,我也不是很懂的
      

  5.   

    FreeAndNil(Fs);
      FreeAndNil(L);  // 只要一释放,立即出错! 
      FreeAndNil(Ms);//把语句的调用次序改一改看看 ~~
    比如:
      FreeAndNil(Fs);
      FreeAndNil(Ms);
      FreeAndNil(L);  // 只要一释放,立即出错! 再比如:
      FreeAndNil(L);  // 只要一释放,立即出错! 
      FreeAndNil(Fs);
      FreeAndNil(Ms);
      

  6.   

    谢谢老大们的参与!!!调试了一下代码,没有抛出异常~~??????怪!!!我这里每次都出错,我都新建了几个工程了,我的是D5,zswangII(伴水清清)(职业清洁工) 老大,你用的是Dx?我目前只有不释放,但有内存泄漏,这个问题真是莫名其妙啊……
      

  7.   

    to zswangII(伴水清清)(职业清洁工) :换来换去还是出错,证明了我的猜想。能不能把阁下没有出错的代码给我发一个,谢谢![email protected]
      

  8.   

    晕,我的代码就是从这里Copy的~~
    不同的是我Delphi6~~
    手里没有Delphi5帮你调试~~再测试下如下代码~~
      FreeAndNil(Fs);
    Application.ProcessMessages;
      FreeAndNil(L);  // 只要一释放,立即出错! 
    Application.ProcessMessages;
      FreeAndNil(Ms);在不然就把L: TLZWUnencode声明成字段,只创建一次,减少内存泄漏~~
      

  9.   

    destructor TLZWEncode.Destroy;
    begin
      FreeMem(ExportBlock);
      inherited;
    end;你試試修改為:
    destructor TLZWEncode.Destroy;
    begin
      ZeroMemory(ExportBlock^, sizeOf(....);
      FreeMem(ExportBlock);
      inherited;
    end;看有沒有用!
      

  10.   

    换了一个2M的Exe压,更邪门,压缩就 Stack overflow,把缓冲区改大,还是出错!压小文件不出错,但解压依然出错!真是吐血.......Damn!
      

  11.   

    谢谢大家,我再试一下,实在不行就用RLE算法...
      

  12.   

    没用过,猛料是集结不少人的经验,但里边问题多多,并且版本一般很老,虽然DELPHI的向下兼容还算比较不错,但不要全不依赖它,不行就改个思路,用其他方法或控件试试。http://218.56.11.178:8018/FileDown.aspx?FID=259
      

  13.   

    谢谢 jpyc(九品御厨-进军嵌入式) 老大,但我现在要对内存数据进行压缩,而ZipTV一般是对文件进行压缩的。刚才试了一下RLE,压缩率低得惊人,1M的文本只减少了几十个字节...郁闷...
      

  14.   

    //调试了不少时间~~
    //不明白按照逻辑上写怎么会少些字节~~
    //通过记录文件大小截取解决,请做测试~~procedure TForm1.Button1Click(Sender: TObject); //压缩过程,正常
    var
      L: TLZWEncode;
      Ms: TMemoryStream;
      Buf: array[0..LZWBUFFER - 1] of Byte;
      Fs: TFileStream;
      I, J: Integer;
    begin
      Fs := TFileStream.Create('C:\temp\TestApp.lzw', fmCreate);
      Ms := TMemoryStream.Create;
      Ms.LoadFromFile('C:\temp\TestApp.exe');
      J := Ms.Size;
      Fs.Write(J, SizeOf(I));
      L := TLZWEncode.Create;
      L.GetBegin;
      for I := 1 to Ms.Size div LZWBUFFER + 1 do begin
        FillChar(Buf, SizeOf(Buf), 0);
        Ms.Read(Buf, LZWBUFFER);
        L.Execute(Buf, LZWBUFFER);
        Fs.Write(L.GetExportPointer^, L.GetExportSize);
      end;
      L.GetEnd;  FreeAndNil(Fs);
      FreeAndNil(L);
      FreeAndNil(Ms);
    end;procedure TForm1.Button2Click(Sender: TObject); //解压缩过程
    var
      L: TLZWUnencode;
      Ms: TMemoryStream;
      Buf: array[0..LZWBUFFER - 1] of Byte;
      Fs: TFileStream;
      I, J: Integer;
    begin
      Fs := TFileStream.Create('C:\temp\TestApp~.exe', fmCreate);
      Ms := TMemoryStream.Create;
      Ms.LoadFromFile('C:\temp\TestApp.lzw');
      Ms.Read(J, SizeOf(J));
      L := TLZWUnencode.Create;
      L.GetBegin;
      repeat
        I := Ms.Read(Buf, LZWBUFFER);
        L.Execute(Buf, I);
        Dec(J, L.GetExportSize);
        if J <= 0 then begin
          Fs.Write(L.GetExportPointer^, L.GetExportSize + J);
          Break;
        end else Fs.Write(L.GetExportPointer^, L.GetExportSize);
      until I = 0;
      L.GetEnd;
      
      FreeAndNil(Fs);
      FreeAndNil(L);
      FreeAndNil(Ms);
    end;
      

  15.   

    //这是前不久用ZLib写的压缩目录的算法,提供参考~~(*//
    标题:压缩和解压目录
    说明:利用ZLib单元;不处理空目录
    设计:Zswang
    日期:2003-09-06
    支持:[email protected]
    //*)///////Begin Source
    uses ZLib, FileCtrl;const cBufferSize = $4096;function FileCompression(mFileName: TFileName; mStream: TStream): Integer;
    var
      vFileStream: TFileStream;
      vBuffer: array[0..cBufferSize]of Char;
      vPosition: Integer;
      I: Integer;
    begin
      Result := -1;
      if not FileExists(mFileName) then Exit;
      if not Assigned(mStream) then Exit;
      vPosition := mStream.Position;
      vFileStream := TFileStream.Create(mFileName, fmOpenRead or fmShareDenyNone);
      with TCompressionStream.Create(clMax, mStream) do try
        for I := 1 to vFileStream.Size div cBufferSize do begin
          vFileStream.Read(vBuffer, cBufferSize);
          Write(vBuffer, cBufferSize);
        end;
        I := vFileStream.Size mod cBufferSize;
        if I > 0 then begin
          vFileStream.Read(vBuffer, I);
          Write(vBuffer, I);
        end;
      finally
        Free;
        vFileStream.Free;
      end;
      Result := mStream.Size - vPosition; //增量
    end; { FileCompression }function FileDecompression(mFileName: TFileName; mStream: TStream): Integer;
    var
      vFileStream: TFileStream;
      vBuffer: array[0..cBufferSize]of Char;
      I: Integer;
    begin
      Result := -1;
      if not Assigned(mStream) then Exit;
      ForceDirectories(ExtractFilePath(mFileName)); //创建目录
      
      vFileStream := TFileStream.Create(mFileName, fmCreate or fmShareDenyWrite);  with TDecompressionStream.Create(mStream) do try
        repeat
          I := Read(vBuffer, cBufferSize);
          vFileStream.Write(vBuffer, I);
        until I = 0;
        Result := vFileStream.Size;
      finally
        Free;
        vFileStream.Free;
      end;
    end; { FileDecompression }function StrLeft(const mStr: string; mDelimiter: string): string;
    begin
      Result := Copy(mStr, 1, Pos(mDelimiter, mStr) - 1);
    end; { StrLeft }function StrRight(const mStr: string; mDelimiter: string): string;
    begin
      if Pos(mDelimiter, mStr) > 0 then
        Result := Copy(mStr, Pos(mDelimiter, mStr) + Length(mDelimiter), MaxInt)
      else Result := '';
    end; { StrRight }type
      TFileHead = packed record
        rIdent: string[3]; //标识
        rVersion: Byte; //版本
      end;const
      cIdent: string[3] = 'zsf';
      cVersion = $01;
      cErrorIdent = -1;
      cErrorVersion = -2;function DirectoryCompression(mDirectory, mFileName: TFileName): Integer;
    var
      vFileInfo: TStrings;
      vFileInfoSize: Integer;
      vFileInfoBuffer: PChar;
      vFileHead: TFileHead;  vMemoryStream: TMemoryStream;
      vFileStream: TFileStream;  procedure pAppendFile(mSubFile: TFileName);
      begin
        vFileInfo.Append(Format('%s|%d',
          [StringReplace(mSubFile, mDirectory + '\', '', [rfReplaceAll, rfIgnoreCase]),
            FileCompression(mSubFile, vMemoryStream)]));
        Inc(Result);
      end; { pAppendFile }  procedure pSearchFile(mPath: TFileName);
      var
        vSearchRec: TSearchRec;
        K: Integer;
      begin
        K := FindFirst(mPath + '\*.*', faAnyFile, vSearchRec);
        while K = 0 do begin
          if (vSearchRec.Attr and faDirectory > 0) and
            (Pos(vSearchRec.Name, '..') = 0) then
            pSearchFile(mPath + '\' + vSearchRec.Name)
          else if Pos(vSearchRec.Name, '..') = 0 then
            pAppendFile(mPath + '\' + vSearchRec.Name);
          K := FindNext(vSearchRec);
        end;
        FindClose(vSearchRec);
      end; { pSearchFile }
    begin
      Result := 0;
      if not DirectoryExists(mDirectory) then Exit;
      vFileInfo := TStringList.Create;
      vMemoryStream := TMemoryStream.Create;
      mDirectory := ExcludeTrailingPathDelimiter(mDirectory);  vFileStream := TFileStream.Create(mFileName, fmCreate or fmShareDenyWrite);
      try
        pSearchFile(mDirectory);
        vFileInfoBuffer := vFileInfo.GetText;
        vFileInfoSize := StrLen(vFileInfoBuffer);    { DONE -oZswang -c添加 : 写入头文件信息 }
        vFileHead.rIdent := cIdent;
        vFileHead.rVersion := cVersion;
        vFileStream.Write(vFileHead, SizeOf(vFileHead));    vFileStream.Write(vFileInfoSize, SizeOf(vFileInfoSize));
        vFileStream.Write(vFileInfoBuffer^, vFileInfoSize);
        vMemoryStream.Position := 0;
        vFileStream.CopyFrom(vMemoryStream, vMemoryStream.Size);
      finally
        vFileInfo.Free;
        vMemoryStream.Free;
        vFileStream.Free;
      end;
    end; { DirectoryCompression }function DirectoryDecompression(mDirectory, mFileName: TFileName): Integer;
    var
      vFileInfo: TStrings;
      vFileInfoSize: Integer;
      vFileHead: TFileHead;  vMemoryStream: TMemoryStream;
      vFileStream: TFileStream;
      I: Integer;
    begin
      Result := 0;
      if not FileExists(mFileName) then Exit;
      vFileInfo := TStringList.Create;
      vMemoryStream := TMemoryStream.Create;
      mDirectory := ExcludeTrailingPathDelimiter(mDirectory);
      vFileStream := TFileStream.Create(mFileName, fmOpenRead or fmShareDenyNone);
      try
        if vFileStream.Size < SizeOf(vFileHead) then Exit;
        { DONE -oZswang -c添加 : 读取头文件信息 }
        vFileStream.Read(vFileHead, SizeOf(vFileHead));
        if vFileHead.rIdent <> cIdent then Result := cErrorIdent;
        if vFileHead.rVersion <> cVersion then Result := cErrorVersion;
        if Result <> 0 then Exit;    vFileStream.Read(vFileInfoSize, SizeOf(vFileInfoSize));
        vMemoryStream.CopyFrom(vFileStream, vFileInfoSize);
        vMemoryStream.Position := 0;
        vFileInfo.LoadFromStream(vMemoryStream);    for I := 0 to vFileInfo.Count - 1 do begin
          vMemoryStream.Clear;
          vMemoryStream.CopyFrom(vFileStream,
            StrToIntDef(StrRight(vFileInfo[I], '|'), 0));
          vMemoryStream.Position := 0;
          FileDecompression(mDirectory + '\' + StrLeft(vFileInfo[I], '|'),
            vMemoryStream);
        end;
        Result := vFileInfo.Count;
      finally
        vFileInfo.Free;
        vMemoryStream.Free;
        vFileStream.Free;
      end;
    end; { DirectoryDeompression }
    ///////End Source///////Begin Demo
    procedure TForm1.ButtonCompressionClick(Sender: TObject);
    begin
      Caption := 'DirectoryCompression:' +
        IntToStr(DirectoryCompression(EditDirectory.Text, EditFileName.Text));
    end;procedure TForm1.ButtonDecompressionClick(Sender: TObject);
    begin
      Caption := 'DirectoryDecompression:' + 
        IntToStr(DirectoryDecompression(EditDirectory.Text, EditFileName.Text));
    end;procedure TForm1.SpeedButtonFileNameClick(Sender: TObject);
    begin
      if not OpenDialog1.Execute then Exit;
      EditFileName.Text := OpenDialog1.FileName;
    end;procedure TForm1.SpeedButtonDirectoryClick(Sender: TObject);
    var
      vDirectory: string;
    begin
      vDirectory := EditDirectory.Text;
      if not SelectDirectory('Select', '', vDirectory) then Exit;
      EditDirectory.Text := vDirectory;
    end;
    ///////End Demo
      

  16.   

    嘿嘿
    用这个保证没有问题的
    我3年前写的UNIT LZRW;INTERFACEuses SysUtils;{$IFDEF WIN32}
    type Int16 = SmallInt;
    {$ELSE}
    type Int16 = Integer;
    {$ENDIF}CONST
        BufferMaxSize  = 32768;
        BufferMax      = BufferMaxSize-1;
        FLAG_Copied    = $80;
        FLAG_Compress  = $40;TYPE
        BufferIndex    = 0..BufferMax + 15; 
        BufferSize     = 0..BufferMaxSize;
           { extra bytes needed here if compression fails      *dh *}
        BufferArray    = ARRAY [BufferIndex] OF BYTE;
        BufferPtr      = ^BufferArray;
        ELzrw1KHCompressor = Class(Exception);
    FUNCTION  Compression    (    Source,Dest    : BufferPtr;
                                  SourceSize     : BufferSize   )    : BufferSize;FUNCTION  Decompression  (    Source,Dest    : BufferPtr;
                                  SourceSize     : BufferSize   )    : BufferSize;IMPLEMENTATIONtype
      HashTable      = ARRAY [0..4095] OF Int16;
      HashTabPtr     = ^Hashtable;VAR
      Hash                     : HashTabPtr;                             { check if this string has already been seen }
                                 { in the current 4 KB window }
    FUNCTION  GetMatch       (    Source         : BufferPtr;
                                  X              : BufferIndex;
                                  SourceSize     : BufferSize;
                                  Hash           : HashTabPtr;
                              VAR Size           : WORD;
                              VAR Pos            : BufferIndex  )    : BOOLEAN;
    VAR
      HashValue      : WORD;
      TmpHash        : Int16;
    BEGIN
      HashValue := (40543*((((Source^[X] SHL 4) XOR Source^[X+1]) SHL 4) XOR
                                         Source^[X+2]) SHR 4) AND $0FFF;
      Result := FALSE;
      TmpHash := Hash^[HashValue];
      IF (TmpHash <> -1) and (X - TmpHash < 4096) THEN BEGIN
        Pos := TmpHash;
        Size := 0;
        WHILE ((Size < 18) AND (Source^[X+Size] = Source^[Pos+Size])
                           AND (X+Size < SourceSize)) DO begin
          INC(Size);
        end;
        Result := (Size >= 3)
      END;
      Hash^[HashValue] := X
    END;
                                        { compress a buffer of max. 32 KB }
    FUNCTION  Compression(Source, Dest : BufferPtr;
                          SourceSize   : BufferSize) :BufferSize;
    VAR
      Bit,Command,Size         : WORD;
      Key                      : Word;
      X,Y,Z,Pos                : BufferIndex;
    BEGIN
      FillChar(Hash^,SizeOf(Hashtable), $FF);
      Dest^[0] := FLAG_Compress;
      X := 0;
      Y := 3;
      Z := 1;
      Bit := 0;
      Command := 0;
      WHILE (X < SourceSize) AND (Y <= SourceSize) DO BEGIN
        IF (Bit > 15) THEN BEGIN
          Dest^[Z] := HI(Command);
          Dest^[Z+1] := LO(Command);
          Z := Y;
          Bit := 0;
          INC(Y,2)
        END;
        Size := 1;
        WHILE ((Source^[X] = Source^[X+Size]) AND (Size < $FFF)
                             AND (X+Size < SourceSize)) DO begin
                  INC(Size);
        end;
        IF (Size >= 16) THEN BEGIN
          Dest^[Y] := 0;
          Dest^[Y+1] := HI(Size-16);
          Dest^[Y+2] := LO(Size-16);
          Dest^[Y+3] := Source^[X];
          INC(Y,4);
          INC(X,Size);
          Command := (Command SHL 1) + 1;
        END
        ELSE begin { not size >= 16 }
          IF (GetMatch(Source,X,SourceSize,Hash,Size,Pos)) THEN BEGIN
            Key := ((X-Pos) SHL 4) + (Size-3);
            Dest^[Y] := HI(Key);
            Dest^[Y+1] := LO(Key);
            INC(Y,2);
            INC(X,Size);
            Command := (Command SHL 1) + 1
          END
          ELSE BEGIN
            Dest^[Y] := Source^[X];
            INC(Y);
            INC(X);
            Command := Command SHL 1
          END;
        end; { size <= 16 }
        INC(Bit);
      END; { while x < sourcesize ... }
      Command := Command SHL (16-Bit);
      Dest^[Z] := HI(Command);
      Dest^[Z+1] := LO(Command);
      IF (Y > SourceSize) THEN BEGIN
        MOVE(Source^[0],Dest^[1],SourceSize);
        Dest^[0] := FLAG_Copied;
        Y := SUCC(SourceSize)
      END;
      Result := Y
    END;                                    { decompress a buffer of max 32 KB }
    FUNCTION  Decompression(Source,Dest    : BufferPtr;
                            SourceSize     : BufferSize) : BufferSize;
    VAR
      X,Y,Pos                  : BufferIndex;
      Command,Size,K           : WORD;
      Bit                      : BYTE;
      SaveY                    : BufferIndex; { * dh * unsafe for-loop variable Y }BEGIN
      IF (Source^[0] = FLAG_Copied) THEN  begin
        FOR Y := 1 TO PRED(SourceSize) DO begin
          Dest^[PRED(Y)] := Source^[Y];
          SaveY := Y;
        end;
        Y := SaveY;
      end
      ELSE BEGIN
        Y := 0;
        X := 3;
        Command := (Source^[1] SHL 8) + Source^[2];
        Bit := 16;
        WHILE (X < SourceSize) DO BEGIN
          IF (Bit = 0) THEN BEGIN
            Command := (Source^[X] SHL 8) + Source^[X+1];
            Bit := 16;
            INC(X,2)
          END;
          IF ((Command AND $8000) = 0) THEN BEGIN
               Dest^[Y] := Source^[X];
               INC(X);
               INC(Y)
          END
          ELSE BEGIN  { command and $8000 }
            Pos := ((Source^[X] SHL 4)
                   +(Source^[X+1] SHR 4));
            IF (Pos = 0) THEN BEGIN
              Size := (Source^[X+1] SHL 8) + Source^[X+2] + 15;
              FOR K := 0 TO Size DO begin
                   Dest^[Y+K] := Source^[X+3];
              end;
              INC(X,4);
              INC(Y,Size+1)
            END
            ELSE BEGIN  { pos = 0 }
              Size := (Source^[X+1] AND $0F)+2;
              FOR K := 0 TO Size DO
                   Dest^[Y+K] := Dest^[Y-Pos+K];
              INC(X,2);
              INC(Y,Size+1)
            END; { pos = 0 }
          END;  { command and $8000 }
          Command := Command SHL 1;
          DEC(Bit)
        END { while x < sourcesize }
      END;
      Result := Y
    END;  { decompression }{
      Unit "Finalization" as Delphi 2.0 would have it
    }var
      ExitSave : Pointer;Procedure Cleanup; far;
    begin
      ExitProc := ExitSave;
      if (Hash <> Nil) then
        Freemem(Hash, Sizeof(HashTable));
    end;
    Initialization  Hash := Nil;
      try
        Getmem(Hash,Sizeof(Hashtable));
      except
        Raise ELzrw1KHCompressor.Create('LZRW1KH : no memory for HASH table');
      end;
      ExitSave := ExitProc;
      ExitProc := @Cleanup;
    END.
    uses
      LZRW;{ TCompressStream }procedure TCompressStream.Compress(InStream, OutStream: TStream; InSize: LongInt);
    var InBuffer, OutBuffer: BufferArray;
        CompressedSize, BytesRead, FinalPos, SizePos, TotalSize: LongInt;
    begin
      TotalSize := 0;
      OutStream.WriteBuffer(FSignature, SizeOf(FSignature));
      SizePos := OutStream.Position;
      OutStream.WriteBuffer(TotalSize, SizeOf(TotalSize));
      while InSize > 0 do
      begin
        BytesRead := InStream.Read(InBuffer, SizeOf(InBuffer));
        CompressedSize := Compression(@InBuffer, @OutBuffer, BytesRead);
        OutStream.WriteBuffer(CompressedSize, SizeOf(CompressedSize));
        OutStream.WriteBuffer(OutBuffer, CompressedSize);
        TotalSize := TotalSize + CompressedSize + SizeOf(CompressedSize);
        InSize := InSize - BytesRead;
      end;
      FinalPos := OutStream.Position;
      OutStream.Position := SizePos;
      OutStream.WriteBuffer(TotalSize, SizeOf(TotalSize));
      OutStream.Position := FinalPos;
    end;procedure TCompressStream.DeCompress(InStream, OutStream: TStream);
    var InBuffer, OutBuffer: BufferArray;
        CompressedSize, UnCompressedSize, InSize: LongInt;
        Sig: array[0..SizeOf(FSignature)-1] of Char;
    begin
      InStream.ReadBuffer(Sig, SizeOf(FSignature));
      if Sig <> FSignature then raise Exception.Create('Wrong file type');
      InStream.ReadBuffer(InSize, SizeOf(InSize));
      while InSize > 0 do
      begin
        InStream.ReadBuffer(CompressedSize, SizeOf(CompressedSize));
        InStream.ReadBuffer(InBuffer, CompressedSize);
        UnCompressedSize := DeCompression(@InBuffer, @OutBuffer, CompressedSize);
        OutStream.WriteBuffer(OutBuffer, UnCompressedSize);
        InSize := InSize - CompressedSize - SizeOf(CompressedSize);
      end;
    end;
      

  17.   

    还有这个简单的压缩算法,好不好用就不知道了
    unit zip;interfaceuses
      Windows, Messages, SysUtils, Classes ;const maxt=2048;
          bsize=4096;
          isize=2048;
          eof_code=256;
          next_len=257;
          start_len=258;
          empty=259;type
      Tzip = class(TComponent)
      private
             tabl:array[0..2049] of integer;
             bbuf:array[0..bsize] of byte;
             ibuf:array[0..isize] of integer;
             tabl_count,bpos,ipos,blen:integer;
             code_len:integer;
             free_bits,full_bits:integer;
             out_c:integer;
             Foutstream:Tstream;
             Finstream:tstream;
             Fetime:integer;
             Fpos:integer;
             outstr:string;
             firstchar:char;
             FOnupdate: TNotifyEvent;
     
          { Private declarations }
        procedure init;
        procedure addtotable(str: integer);
        procedure setfinstream(const Value: Tstream);
        procedure setfoutstream(const Value: Tstream);
        procedure open_unzip;
        procedure open_zip;
        procedure close_zip;
        procedure close_unzip;
        function getbyte: integer;
        function indexof(str, ch: integer): integer;
        procedure putcode(code: integer);
        procedure put_str(ix: integer; first: boolean);
        function read_code: integer;  protected
      public
        procedure Zip;
        procedure Unzip;
        property instream:Tstream write setfinstream;
        property outstream:Tstream write setfoutstream;
      published
         property Elapsed:integer read fetime;
          property Onupdate:TNotifyEvent read fonupdate write fonupdate;
          property Position:integer read fpos;
      end;procedure Register;implementationprocedure Register;
    begin
      RegisterComponents( 'Custom', [Tzip]);
    end;
    procedure tzip.init;
    var i:integer;
    begin
         code_len:=9;
         tabl_count:=empty;
         for i:=0 to 255 do tabl[i]:=$ffff00+i;
    end;
    procedure tzip.addtotable(str:integer);
    begin
         if tabl_count>=maxt then tabl_count:=empty
         else
         begin
              tabl[tabl_count]:=str;
              inc(tabl_count);
         end;
    end;
    procedure tzip.open_zip;
    begin
         blen:=finstream.Read(bbuf,bsize);
         bpos:=0;
         ipos:=0;
         out_c:=0;
         free_bits:=32;
         fpos:=0;
    end;
    procedure tzip.close_zip;
    begin
         if ipos>0 then foutstream.Write(ibuf,ipos*4);
    end;
    procedure tzip.close_unzip;
    begin
         if bpos>0 then foutstream.Write(bbuf,bpos);
    end;
    procedure tzip.open_unzip;
    begin
         full_bits:=0;
         ipos:=0;
         bpos:=0;
         finstream.Read(ibuf,isize*4);
         fpos:=0;
    end;function tzip.getbyte:integer;
    begin
         if (bpos>=blen) then
         begin
              if blen<bsize then
              begin
                   result:=eof_code;
                   exit;
              end
             else
              begin
                   blen:=finstream.Read(bbuf,bsize);
                   bpos:=0;
                   fpos:=finstream.Position;
                   if assigned(fonupdate) then fonupdate(nil);
              end;
         end;
         result:=bbuf[bpos];
         inc(bpos);
    end;function tzip.indexof(str,ch:integer):integer;
    var cd,mn,mx:integer;
    begin
         cd:=(str shl 8) + ch;
         mn:=str+1;if mn<empty then mn:=empty;
         mx:=tabl_count-1;
         for result:=mn to mx do if tabl[result]=cd then exit;
         result:=-1;
    end;
    procedure tzip.putcode(code:integer);
    var shift_bits,tc,tf:integer;    procedure store_out_c;
        begin
              ibuf[ipos]:=out_c;
              inc(ipos);
              if ipos>=isize then
              begin
                   foutstream.Write(ibuf,isize*4);
                   ipos:=0;
              end;
              out_c:=0;
              free_bits:=32;
        end;begin
         shift_bits:=free_bits-code_len;     if shift_bits<0 then
         begin
              tf:=free_bits;
              shift_bits:=-shift_bits;
              tc:=code shr shift_bits;
              out_c:=out_c or tc;
              store_out_c;
              shift_bits:=32-shift_bits;
              inc(free_bits,tf);          //add alredy pushed bits
         end;     tc:=code shl shift_bits;
         out_c:=out_c or tc;
         dec(free_bits,code_len);
         if (free_bits=0) or (code=eof_code) then store_out_c;
    end;
    procedure tzip.zip;
    var str,ch,t:integer;
    begin
         init;
         open_zip;
         Fetime:=gettickcount;
         str:=getbyte;
         repeat
               ch:=getbyte;
               if ch=eof_code then break;
               t:=indexof(str,ch);
               if t<>-1 then str:=t else
               begin
                    putcode(str);
                    if (tabl_count=512) or (tabl_count=1024) then
                    begin
                         putcode(next_len);
                         inc(code_len);
                    end;
                    if tabl_count=maxt then
                    begin
                         putcode(start_len);
                         code_len:=9;
                    end;
                    addtotable((str shl 8) +ch);
                    str:=ch;
               end;
         until false;
         putcode(str);
         putcode(eof_code);
         fetime:=gettickcount-fetime;
         close_zip;
    end;
    procedure tzip.put_str(ix:integer;first:boolean);
    var i,l:integer;
    begin
         outstr:='';
         repeat
               i:=tabl[ix];
               ix:=i shr 8;
               outstr:=outstr+chr(i and $ff);
         until ix=$ffff;     l:=length(outstr);     for i:=l downto 1 do
         begin
              bbuf[bpos]:=byte(outstr[i]);
              inc(bpos);
              if bpos>=bsize then
              begin
                   foutstream.Write(bbuf,bsize);
                   bpos:=0;
              end
         end;     firstchar:=outstr[l];
         if first then
         begin
              bbuf[bpos]:=byte(firstchar);
              inc(bpos);
              if bpos>=bsize then
              begin
                   foutstream.Write(bbuf,bsize);
                   bpos:=0;
              end
         end;
    end;
    function tzip.read_code:integer;
    var mask,shift_bits,tf:integer;procedure get_out_c;
    begin
         out_c:=ibuf[ipos];
         inc(ipos);
         if ipos>=isize then
         begin
              finstream.Read(ibuf,isize*4);
              ipos:=0;
              fpos:=finstream.Position;
              if assigned(fonupdate) then fonupdate(nil);
         end;
         full_bits:=32;
    end;begin
        if full_bits=0 then get_out_c;     mask:=(1 shl code_len) -1; //0111111
         result:=0;     shift_bits:=full_bits-code_len;     if shift_bits<0 then
         begin
              tf:=full_bits;
              shift_bits:=-shift_bits;
              result:=(out_c shl shift_bits) and mask;
              get_out_c;
              shift_bits:=32-shift_bits;
              inc(full_bits,tf);
         end;
         result:=result or ((out_c shr shift_bits) and mask);
         dec(full_bits,code_len);
    end;procedure tzip.unzip;
    var old,cod:integer;
    begin
         init;
         open_unzip;
         fetime:=gettickcount;
         old:=read_code;
         put_str(old,false);
         while true do
         begin
              cod:=read_code;
              if cod=eof_code then break;
              if cod=next_len then begin inc(code_len);continue;end;
              if cod=start_len then begin code_len:=9;continue;end;
              if cod>=tabl_count then put_str(old,true) else put_str(cod,false);
              addtotable(old shl 8 +byte(firstchar));
              old:=cod;
         end;
         fetime:=gettickcount-fetime;
         close_unzip;
    end;procedure Tzip.setfinstream(const Value: Tstream);
    begin
      finstream := Value;
    end;procedure Tzip.setfoutstream(const Value: Tstream);
    begin
      foutstream := Value;
    end;
    end.
      

  18.   

    再UP一下,hehe^^crossbow(【带三尺剑立不世之功】) 兄弟的贴子不错,等问题解决了,再收藏一下
      

  19.   

    我来试试,先向两位表示感谢!!!trying...
      

  20.   

    终于解决了!感谢zswangII(伴水清清)(职业清洁工) 老大和ly_liuyang(Liu Yang) 老大的大力协助!!!两位老大的代码有异曲同工之妙。我出错之处在于强行将整个文件放入缓冲区压缩,从而导致栈溢出。猛料中的LZW代码没有错,是我自己太弱智了。两位老大都采用了类似对称加密算法的分组压缩模式,避免了缓冲区溢出。但新问题在于解压缩以前不知道原始文件的大小,于是两位老大都把原始文件大小写入文件头,LiuYang老大还加了一个压缩标记加以识别。当然,我们自己还可以加入CRC效验值。我用了一个 40M 的文本文件进行测试,zswangII的Lzw、LiuYang的Lzrw和Zip单元都顺利通过,经内存清道夫检查都没有内存泄漏。解压缩后和原文件进行了比较也都完全一样。Lzw压缩后25M,Lzrw压缩后24M,Zip压缩后28M;Lzw速度最快,Lzrw稍慢一点,Zip单元不知道用的什么算法,慢得出奇,让人无法忍受。to zswangII(伴水清清)(职业清洁工)老大阁下:还有一个疑问,我后来把缓冲区 LZWBUFFER(编码处理缓存)、LZWSTACKBUFFERSIZE(栈缓存容量)和LZWEXPORTBLOCKSIZE(输出缓存容量)的值都改为$FFFFFF了,已经远大于我要处理的文件了,怎么还是溢出?感谢老大们的赏光,虽然分对你们算不了什么,但还是请zswangII(伴水清清)(职业清洁工) 老大和ly_liuyang(Liu Yang) 老大各接100分,佩服你们的水平,以后还请多多指教:zswangII(伴水清清)(职业清洁工) 老大:
    http://expert.csdn.net/Expert/topic/2593/2593971.xml?temp=.7814752ly_liuyang(Liu Yang) 老大: 
    http://expert.csdn.net/Expert/topic/2593/2593979.xml?temp=.2123377
      

  21.   

    第二章 技术准备:概率、模型和编码什么是熵数据压缩不仅起源于 40 年代由 Claude Shannon 首创的信息论,而且其基本原理即信息究竟能被压缩到多小,至今依然遵循信息论中的一条定理,这条定理借用了热力学中的名词“熵”( Entropy )来表示一条信息中真正需要编码的信息量:考虑用 0 和 1 组成的二进制数码为含有 n 个符号的某条信息编码,假设符号 Fn 在整条信息中重复出现的概率为 Pn,则该符号的熵也即表示该符号所需的位数位为:En = - log2( Pn )
    整条信息的熵也即表示整条信息所需的位数为:E = ∑En举个例子,对下面这条只出现了 a b c 三个字符的字符串:aabbaccbaa字符串长度为 10,字符 a b c 分别出现了 5 3 2 次,则 a b c 在信息中出现的概率分别为 0.5 0.3 0.2,他们的熵分别为:Ea = -log2(0.5) = 1
    Eb = -log2(0.3) = 1.737
    Ec = -log2(0.2) = 2.322
    整条信息的熵也即表达整个字符串需要的位数为:E = Ea * 5 + Eb * 3 + Ec * 2 = 14.855 位
    回想一下如果用计算机中常用的 ASCII 编码,表示上面的字符串我们需要整整 80 位呢!现在知道信息为什么能被压缩而不丢失原有的信息内容了吧。简单地讲,用较少的位数表示较频繁出现的符号,这就是数据压缩的基本准则。细心的读者马上会想到,我们该怎样用 0 1 这样的二进制数码表示零点几个二进制位呢?确实很困难,但不是没有办法。一旦我们找到了准确表示零点几个二进制位的方法,我们就有权利向无损压缩的极限挑战了。不要着急,看到第四章就明白了。模型从上面的描述,我们明白,要压缩一条信息,首先要分析清楚信息中每个符号出现的概率。不同的压缩程序通过不同的方法确定符号的出现概率,对符号的概率计算得越准确,也就越容易得到好的压缩效果。在压缩程序中,用来处理输入信息,计算符号的概率并决定输出哪个或哪些代码的模块叫做模型。难道对信息中字符的出现概率这么难以估计以至于有各种不同的压缩模型吗?对上面的字符串我们不是很容易就知道每个字符的概率了吗?是的是的,不过上面的字符串仅有 10 个字符长呀,那只是例子而已。考虑我们现实中要压缩的文件,大多数可是有几十 K 甚至几百 K 长,几 M 字节的文件不是也屡见不鲜吗?是的,我们可以预先扫描文件中的所有字符,统计出每个字符出现的概率,这种方法在压缩术语里叫做“静态统计模型”。但是,不同的文件中,字符有不同的分布概率,我们要么先花上大量的时间统计我们要压缩的所有文件中的字符概率,要么为每一个单独的文件保存一份概率表以备解压缩时需要。糟糕的是,不但扫描文件要消耗大量时间,而且保存一份概率表也使压缩后的文件增大了不少。所以,在实际应用中,“静态统计模型”应用的很少。真正的压缩程序中使用的大多是一种叫“自适应模型”的东西。自适应模型可以说是一台具有学习功能的自动机。他在信息被输入之前对信息内容一无所知并假定每个字符的出现概率均等,随着字符不断被输入和编码,他统计并纪录已经出现过的字符的概率并将这些概率应用于对后续字符的编码。也就是说,自适应模型在压缩开始时压缩效果并不理想,但随着压缩的进行,他会越来越接近字符概率的准确值,并达到理想的压缩效果。自适应模型还可以适应输入信息中字符分布的突然变化,可以适应不同的文件中的字符分布而不需要保存概率表。上面提到的模型可以统称为“统计模型”,因为他们都是基于对每个字符出现次数的统计得到字符概率的。另一大类模型叫做“字典模型”。实际上,当我们在生活中提到“工行”这个词的时候,我们都知道其意思是指“中国工商银行”,类似的例子还有不少,但共同的前提是我们心中都有一本约定俗成的缩写字典。字典模型也是如此,他并不直接计算字符出现的概率,而是使用一本字典,随着输入信息的读入,模型找出输入信息在字典中匹配的最长的字符串,然后输出该字符串在字典中的索引信息。匹配越长,压缩效果越好。事实上,字典模型本质上仍然是基于对字符概率的计算的,只不过,字典模型使用整个字符串的匹配代替了对某一字符重复次数的统计。可以证明,字典模型得到的压缩效果仍然无法突破熵的极限。当然,对通用的压缩程序来说,保存一本大字典所需的空间仍然是无法让人忍受的,况且,任何一本预先定义的字典都无法适应不同文件中数据的变化情况。对了,字典模型也有相应的“自适应”方案。我们可以随着信息的不断输入,从已经输入的信息中建立合适的字典,并不断更新这本字典,以适应数据的不断变化。让我们从另一个角度理解一下自适应模型。Cluade Shannon 曾试图通过一个“聚会游戏”(party game)来测定英语的真实信息容量。他每次向听众公布一条被他隐藏起一个字符的消息,让听众来猜下一个字符是什么,一次猜一个,直到猜对为止。然后,Shannon 使用猜测次数来确定整个信息的熵。在这个实验中,一种根据前面出现过的字符估计下一个字符概率的模型就存在于听众的头脑中,比计算机中使用的自适应模型更为高级的是,听众除了根据字符出现过的次数外,还可以根据他们对语言的经验进行猜测。编码通过模型,我们已经确定了对某一个符号该用多少位二进制数进行编码。现在的问题是,如何设计一种编码方案,使其尽量精确地用模型计算出来的位数表示某个符号。最先被考虑的问题是,如果对 a 用 3 个二进制位就可以表示,而对 b 用 4 个二进制位就可以表示,那么,在解码时,面对一连串的二进制流,我怎么知道哪三个位是 a,哪四个位是 b 呢?所以,必须设计出一种编码方式,使得解码程序可以方便地分离每个字符的编码部分。于是有了一种叫“前缀编码”的技术。该技术的主导思想是,任何一个字符的编码,都不是另一个字符编码的前缀。反过来说就是,任何一个字符的编码,都不是由另一个字符的编码加上若干位 0 或 1 组成。看一下前缀编码的一个最简单的例子:
      符号        编码
       A           0
       B           10
       C           110
       D           1110
       E           11110有了上面的码表,你一定可以轻松地从下面这串二进制流中分辨出真正的信息内容了:1110010101110110111100010 - DABBDCEAAB
    下一个问题是:象上面这样的前缀编码只能表示整数位的符号,对几点几位的符号只能用近似的整数位输出,那么怎样输出小数位数呢?科学家们用算术编码解决了这个问题,我们将在第四章对算术编码作详细的讨论。总结一下不同的模型使用不同的方法计算字符的出现概率,由此概率可以得出字符的熵;然后使用不同的编码方法,尽量接近我们期望得到的熵值。所以,压缩效果的好坏一方面取决于模型能否准确地得到字符概率,另一方面也取决于编码方法能否准确地用期望的位数输出字符代码。换句话说,压缩 = 模型 + 编码。如下图所示:
    ---------  符号   ----------  概率   ----------  代码   ----------
    |  输入 |-------->|  模型  |-------->|  编码  |-------->|  输出  |
    ---------         ----------         ----------         ---------- 
    资源我们已经知道,编写压缩程序往往不能对数据的整个字节进行处理,而是要按照二进制位来读写和处理数据,操作二进制位的函数也就成为了压缩程序中使用最为普遍的工具函数。我们在此提供两组函数集,使用它们可以有效的进行文件或内存中的二进制位操作。它们共有六个文件:bitio.h - 用于文件中二进制位操作的函数说明。bitio.cpp - 用于文件中二进制位操作的函数实现。errhand.h 和 errhand.cpp - bitio.cpp 中使用的错误处理函数。wm_bitio.h - 用于内存中二进制位操作的函数说明。wm_bitio.cpp - 用于内存中二进制位操作的函数实现。它们被共同包装在文件 bitio.zip 中。
      

  22.   

    第三章 奇妙的二叉树:Huffman的贡献提起 Huffman 这个名字,程序员们至少会联想到二叉树和二进制编码。的确,我们总以 Huffman 编码来概括 D.A.Huffman 个人对计算机领域特别是数据压缩领域的杰出贡献。我们知道,压缩 = 模型 + 编码,作为一种压缩方法,我们必须全面考虑其模型和编码两个模块的功效;但同时,模型和编码两个模块又相互具有独立性。举例来说,一个使用 Huffman 编码方法的程序,完全可以采用不同的模型来统计字符在信息中出现的概率。因此,我们这一章将首先围绕 Huffman 先生最为重要的贡献 —— Huffman 编码展开讨论,随后,我们再具体介绍可以和 Huffman 联合使用的概率模型。为什么是二叉树为什么压缩领域中的编码方法总和二叉树联系在一起呢?原因非常简单,回忆一下我们介绍过的“前缀编码”:为了使用不固定的码长表示单个字符,编码必须符合“前缀编码”的要求,即较短的编码决不能是较长编码的前缀。要构造符合这一要求的二进制编码体系,二叉树是最理想的选择。考察下面这棵二叉树:                根(root)
                0     |     1
               +------+------+
          0    |    1     0  |   1
         +-----+-----+   +---+----+
         |           |   |        |
         a           |   d        e
                0    |    1
               +-----+-----+
               |           |
               b           c
    要编码的字符总是出现在树叶上,假定从根向树叶行走的过程中,左转为0,右转为1,则一个字符的编码就是从根走到该字符所在树叶的路径。正因为字符只能出现在树叶上,任何一个字符的路径都不会是另一字符路径的前缀路径,符合要求的前缀编码也就构造成功了:a - 00  b - 010  c - 011  d - 10  e - 11
    Shannon-Fano 编码进入 Huffman 先生构造的神奇二叉树之前,我们先来看一下它的前身,由 Claude Shannon 和 R.M.Fano 两人提出的 Shannon-Fano 编码。讨论之前,我们假定要编码字符的出现概率已经由某一模型统计出来,例如,对下面这串出现了五种字符的信息( 40 个字符长 ):cabcedeacacdeddaaabaababaaabbacdebaceada
    五种字符的出现次数分别:a - 16,b - 7,c - 6,d - 6,e - 5。Shannon-Fano 编码的核心仍然是构造二叉树,构造的方式非常简单:1) 将给定符号按照其频率从大到小排序。对上面的例子,应该得到:    a - 16
        b - 7
        c - 6
        d - 6
        e - 5
    2) 将序列分成上下两部分,使得上部频率总和尽可能接近下部频率总和。我们有:    a - 16
        b - 7
    -----------------
        c - 6
        d - 6
        e - 5
    3) 我们把第二步中划分出的上部作为二叉树的左子树,记 0,下部作为二叉树的右子树,记 1。4) 分别对左右子树重复 2 3 两步,直到所有的符号都成为二叉树的树叶为止。现在我们有如下的二叉树:                根(root)
                0     |     1
               +------+------+
          0    |    1     0  |   1
         +-----+-----+   +---+----+
         |           |   |        |
         a           b   c        |
                             0    |    1
                            +-----+-----+
                            |           |
                            d           e
    于是我们得到了此信息的编码表:a - 00  b - 01  c - 10  d - 110  e - 111
    可以将例子中的信息编码为:cabcedeacacdeddaaabaababaaabbacdebaceada
    10 00 01 10 111 110 111 00 10 00 10 ......
    码长共 91 位。考虑用 ASCII 码表示上述信息需要 8 * 40 = 240 位,我们确实实现了数据压缩。Huffman 编码Huffman 编码构造二叉树的方法和 Shannon-Fano 正好相反,不是自上而下,而是从树叶到树根生成二叉树。现在,我们仍然使用上面的例子来学习 Huffman 编码方法。1) 将各个符号及其出现频率分别作为不同的小二叉树(目前每棵树只有根节点)。   a(16)     b(7)    c(6)    d(6)    e(5)
    2) 在 1 中得到的树林里找出频率值最小的两棵树,将他们分别作为左、右子树连成一棵大一些的二叉树,该二叉树的频率值为两棵子树频率值之和。对上面的例子,我们得到一个新的树林:                                     | (11)
       a(16)     b(7)     c(6)       +---+---+        
                                     |       |
                                     d       e
    3) 对上面得到的树林重复 2 的做法,直到所有符号都连入树中为止。这一步完成后,我们有这样的二叉树:                根(root)
                0     |     1
               +------+----------------+
               |              0        |          1
               |             +---------+-----------+
               |      0      |     1        0      |      1
               a     +-------+------+      +-------+-------+
                     |              |      |               |
                     b              c      d               e 
    由此,我们可以建立和 Shannon-Fano 编码略微不同的编码表:   a - 0    b - 100    c - 101    d - 110    e - 111
    对例子中信息的编码为:
      

  23.   


    cabcedeacacdeddaaabaababaaabbacdebaceada
    101 0 100 101 111 110 111 0 101 0 101 ......
    码长共 88 位。这比使用 Shannon-Fano 编码要更短一点。让我们回顾一下熵的知识,使用我们在第二章学到的计算方法,上面的例子中,每个字符的熵为:Ea = - log2(16 / 40) = 1.322
    Eb = - log2( 7 / 40) = 2.515
    Ec = - log2( 6 / 40) = 2.737
    Ed = - log2( 6 / 40) = 2.737
    Ee = - log2( 5 / 40) = 3.000
    信息的熵为:E = Ea * 16 + Eb * 7 + Ec * 6 + Ed * 6 + Ee * 5 = 86.601
    也就是说,表示该条信息最少需要 86.601 位。我们看到,Shannon-Fano 编码和 Huffman 编码都已经比较接近该信息的熵值了。同时,我们也看出,无论是 Shannon-Fano 还是 Huffman,都只能用近似的整数位来表示单个符号,而不是理想的小数位。我们可以将它们做一个对比:   符号      理想位数     S-F 编码    Huffman 编码
                 ( 熵 )       需要位数    需要位数
     ----------------------------------------------------
        a         1.322         2           1
        b         2.515         2           3
        c         2.737         2           3
        d         2.737         3           3
        e         3.000         3           3
     ----------------------------------------------------
      总 计      86。601        91          88
    这就是象 Huffman 这样的整数位编码方式无法达到最理想的压缩效果的原因。为 Huffman 编码选择模型(附范式 Huffman 编码)最简单,最容易被 Huffman 编码利用的模型是“静态统计模型”,也就是说在编码前统计要编码的信息中所有字符的出现频率,让后根据统计出的信息建立编码树,进行编码。这种模型的缺点是显而易见的:首先,对数据量较大的信息,静态统计要消耗大量的时间;其次,必须保存统计出的结果以便解码时构造相同的编码树,或者直接保存编码树本身,而且,对于每次静态统计,都有不同的结果,必须分别予以保存,这要消耗大量的空间(这意味着压缩效率的下降);再次,事实上,即使不将编码树计算在内,对通常含有 0 - 255 字符集的计算机文件来说,静态统计模型统计出的频率是字符在整个文件中的出现频率,往往反映不出字符在文件中不同局部出现频率的变化情况,使用这一频率进行压缩,大多数情况下得不到太好压缩效果,文件有时甚至在压缩后反而增大了。所以,“静态统计模型”一般仅作为复杂算法的某一部分出现,在信息的某一局部完成压缩功能。我们很难将其用于独立的压缩系统。有一种有效的“静态统计模型”的替代方案,如果我们要压缩的所有信息具有某些共同的特性,也即在分布上存在着共同的特征,比如我们要压缩的是普通的英文文本,那么,字母 a 或者字母 e 的出现频率应当是大致稳定的。使用语言学家事先已经建立好的字母频率表来进行压缩和解压缩,不但不用保存多份统计信息,而且一般说来对该类文件有着较好的压缩效果。这种方案除了适应性不太强以外,偶尔还会有一些尴尬的时候。读一遍下面这段话:If Youth,throughout all history, had had a champion to stand up for it; to show a doubting world that a child can think;and, possibly, do it practically; you wouldn't constantly run across folks today who claim that "a child don't know anything." - Gadsby by E.V.Wright, 1939.发现什么问题了吗?哦,整段话中竟没有出现一次英文中出现频率最高的字母 e !真让人惊讶,但没有办法,事先拟定的频率分布总有意外的时候。对英文或中文文本,有一种比较实用的静态模型:不是把字符而是把英文单词或中文词语作为统计频率和编码的单位进行压缩。也就是说,每次编码的不再是 a b c 这样的单个符号,而是 the look flower 这样的单词。这种压缩方式可以达到相当不错的压缩效果,并被广泛地用于全文检索系统。对基于词的编码方式,需要解决几个技术难点。首先是分词的问题,英文单词可以由词间空格分隔,但中文怎么办呢?其实,有很多中文分词算法可以解决这个问题,本书就不再详细介绍了。王笨笨就曾开发过一个不错的分词模块,但希望通过收取一定报酬的方式提供该模块,如有需要,请和王笨笨 E-Mail 联系。一旦我们将词语分离出来,我们就可以对每个词进行频率统计,然后建立 Huffman 编码树,输出编码时,一个编码将代替一个词语。但要注意,英文和汉语的单词数量都在几万到十几万左右,也就是说,我们的 Huffman 编码树将拥有十几万个叶子节点,这对于一棵树来说太大太大了,系统将无力承担所需要的资源,这怎么办呢?我们可以暂时抛开树结构,采用另一种构造 Huffman 编码的方式——范式 Huffman 编码。范式 Huffman 编码(Canonical Huffman Code)的基本思路是:并非只有使用二叉树建立的前缀编码才是 Huffman 编码,只要符合(1)是前缀编码(2)某一字符编码长度和使用二叉树建立的该字符的编码长度相同这两个条件的编码都可以叫做 Huffman 编码。考虑对下面六个单词的编码:  符号   出现次数   传统 Huffman 编码    范式 Huffman 编码
    ------------------------------------------------------------
      单词1     10           000                 000
      单词2     11           001                 001
      单词3     12           100                 010
      单词4     13           101                 011
      单词5     22           01                  10
      单词6     23           11                  11
    注意到范式 Huffman 编码的独特之处了吗?你无法使用二叉树来建立这组编码,但这组编码确实能起到和 Huffman 编码相同的作用。而且,范式 Huffman 编码具有一个明显的特点:当我们把要编码的符号按照其频率从小到大排列时,如果把范式 Huffman 编码本身作为单词的话,也呈现出从小到大的字典顺序。构造范式 Huffman 编码的方法大致是:1) 统计每个要编码符号的频率。2) 根据这些频率信息求出该符号在传统 Huffman 编码树中的深度(也就是表示该符号所需要的位数 - 编码长度)。因为我们关心的仅仅是该符号在树中的深度,我们完全没有必要构造二叉树,仅用一个数组就可以模拟二叉树的创建过程并得到符号的深度,具体方法这里就不详述了。3) 分别统计从最大编码长度 maxlength 到 1 的每个长度对应了多少个符号。根据这一信息从 maxlength 个 0 开始以递增顺序为每个符号分配编码。例如,编码长度为 5 的符号有 4 个,长度为 3 的有 1 个,长度为 2 的有 3 个,则分配的编码依次为: 00000 00001 00010 00011 001 01 10 114) 编码输出压缩信息,并保存按照频率顺序排列的符号表,然后保存每组同样长度编码中的最前一个编码以及该组中的编码个数。现在完全可以不依赖任何树结构进行高速解压缩了。而且在整个压缩、解压缩过程中需要的空间比传统 Huffman 编码少得多。最后要提到的是,Huffman 编码可以采用自适应模型,根据已经编码的符号频率决定下一个符号的编码。这时,我们无需为解压缩预先保存任何信息,整个编码是在压缩和解压缩过程中动态创建的,而且自适应编码由于其符号频率是根据信息内容的变化动态得到的,更符合符号的局部分布规律,因此在压缩效果上比静态模型好许多。但是,采用自适应模型必须考虑编码表的动态特性,即编码表必须可以随时更新以适应符号频率的变化。对于 Huffman 编码来说,我们很难建立能够随时更新的二叉树,使用范式 Huffman 编码是个不错的选择,但依然存在不少技术上的难题。幸好,如果愿意的话,我们可以暂时不考虑自适应模型的 Huffman 编码,因为对于自适应模型我们还有许多更好的选择,下面几章将要谈到的算术编码、字典编码等更为适合采用自适应模型,我们将在其中深入探讨自适应模型的各种实现方法。
      

  24.   

    第四章 向极限挑战:算术编码我们在上一章中已经明白,Huffman 编码使用整数个二进制位对符号进行编码,这种方法在许多情况下无法得到最优的压缩效果。假设某个字符的出现概率为 80%,该字符事实上只需要 -log2(0.8) = 0.322 位编码,但 Huffman 编码一定会为其分配一位 0 或一位 1 的编码。可以想象,整个信息的 80% 在压缩后都几乎相当于理想长度的 3 倍左右,压缩效果可想而知。难道真的能只输出 0.322 个 0 或 0.322 个 1 吗?是用剪刀把计算机存储器中的二进制位剪开吗?计算机真有这样的特异功能吗?慢着慢着,我们不要被表面现象所迷惑,其实,在这一问题上,我们只要换一换脑筋,从另一个角度……哎呀,还是想不通,怎么能是半个呢?好了,不用费心了,数学家们也不过是在十几年前才想到了算术编码这种神奇的方法,还是让我们虚心地研究一下他们究竟是从哪个角度找到突破口的吧。输出:一个小数更神奇的事情发生了,算术编码对整条信息(无论信息有多么长),其输出仅仅是一个数,而且是一个介于 0 和 1 之间的二进制小数。例如算术编码对某条信息的输出为 1010001111,那么它表示小数 0.1010001111,也即十进制数 0.64。咦?怎么一会儿是表示半个二进制位,一会儿又是输出一个小数,算术编码怎么这么古怪呀?不要着急,我们借助下面一个简单的例子来阐释算术编码的基本原理。为了表示上的清晰,我们暂时使用十进制表示算法中出现的小数,这丝毫不会影响算法的可行性。考虑某条信息中可能出现的字符仅有 a b c 三种,我们要压缩保存的信息为 bccb。在没有开始压缩进程之前,假设我们对 a b c 三者在信息中的出现概率一无所知(我们采用的是自适应模型),没办法,我们暂时认为三者的出现概率相等,也就是都为 1/3,我们将 0 - 1 区间按照概率的比例分配给三个字符,即 a 从 0.0000 到 0.3333,b 从 0.3333 到 0.6667,c 从 0.6667 到 1.0000。用图形表示就是:               +-- 1.0000
                   |
       Pc = 1/3    |
                   |
                   +-- 0.6667
                   |
       Pb = 1/3    |
                   |
                   +-- 0.3333
                   |
       Pa = 1/3    |
                   |
                   +-- 0.0000
    现在我们拿到第一个字符 b,让我们把目光投向 b 对应的区间 0.3333 - 0.6667。这时由于多了字符 b,三个字符的概率分布变成:Pa = 1/4,Pb = 2/4,Pc = 1/4。好,让我们按照新的概率分布比例划分 0.3333 - 0.6667 这一区间,划分的结果可以用图形表示为:               +-- 0.6667
       Pc = 1/4    |
                   +-- 0.5834
                   |
                   |
       Pb = 2/4    |
                   |
                   |
                   +-- 0.4167
       Pa = 1/4    |
                   +-- 0.3333
    接着我们拿到字符 c,我们现在要关注上一步中得到的 c 的区间 0.5834 - 0.6667。新添了 c 以后,三个字符的概率分布变成 Pa = 1/5,Pb = 2/5,Pc = 2/5。我们用这个概率分布划分区间 0.5834 - 0.6667:               +-- 0.6667
                   |
       Pc = 2/5    |
                   |
                   +-- 0.6334
                   |
       Pb = 2/5    |
                   |
                   +-- 0.6001
       Pa = 1/5    |
                   +-- 0.5834
    现在输入下一个字符 c,三个字符的概率分布为:Pa = 1/6,Pb = 2/6,Pc = 3/6。我们来划分 c 的区间 0.6334 - 0.6667: