Delphi Base64編碼/解碼及ZLib壓縮/解壓


最近在寫的程序與SOAP相關,所以用到了一些Base64編碼/解碼及數據壓縮/解壓方面的知識. 在這里來作一些總結:

 
一.Base64編碼/解碼
 
  一般用到的是Delphi自帶的單元EncdDecd,當然還有第三方提供的單元或控件,其中我所接觸到的認為比較好的有Indy的TIdMimeEncode / TIdMimeDecode組件,以及RjMime單元.
 
  在這里主要想講講如何才能獲得最好的編碼/解碼性能,EncdDecd提供了EncodeStream/DecodeString, EncodeString/DecodeString兩對函數,如果你使用EncodeString/DecodeString,這沒有什麽可爭議,效率是死的,如果你使用了EncodeStream/DecodeStream,這里面可大有文章了. 先來看看兩個函數的聲明:
procedure EncodeStream(Input, Output: TStream);
procedure DecodeStream(Input, Output: TStream);
  很明了, 兩個參數,都為TStream, TStream是抽象類, 其派生類主要有TMomoryStream,TStringStream,TFileStream等,都可以作為參數傳遞進去,對於Input參數,無論TMemoryStream, TStringStream, TFileStream都不會影響性能,但是對於Output參數,由於壓縮的結果是寫住OutputStream,因此壓縮過程中不斷地執行TStream的Write方法,如果是TMemoryStream,那效率真是太糟糕了,我作過測試,編碼一個5M多的文件,要十幾秒鍾!但如果是TStringStream呢,只要0.2~0.3秒! 這究竟是為什麽呢,因為TMemoryStream里不斷調用Write方法的結果是,不斷地向Windows要求分配內存!從而導致性能下降!而TStringStream和TFileStream則沒有這個問題. 因此,在這里極力向朋友們建議,Output參數最好不用TMemoryStream.
  不過不要緊,你一定要用的話,也是有方法解決性能下降這個問題的! 因為效率下降的原因是不斷的申請內存空間,我們可以從這個方向首手,能不能一次性給它分配好內存空間呢,如果我們事先能確定編碼或解碼后的數據大小(字節數),那麽這是可行的. 問題的關鍵就是如何確定這個編碼或解碼后的字節數了. 對於EncdDecd,我可以給出這個計算方法:
  (1)假設編碼前的字節數為X,那麽編碼后的字節數為 (X + 2) div 3 * 4. 不過,要對EncdDecd進行相應的修改,找到這一小段: 
   if K > 75 then     
   begin
    BufPtr[0] := #$0D;
    BufPtr[1] := #$0A;
    Inc(BufPtr, 2);
    K := 0;
   end; 
  將其注釋掉, 因為這其實是沒什麽用的,只是用來對編碼后的字符串分行的~,我們可以注釋后將單元另存為EncdDecdEx,以后就使用它了!!!
  (2)假設解碼前的字節數是X,那麽解碼后的字節數約為 (X + 3) div 4 * 3
*****注:與編碼不同的是,解碼的字節數不是確定的,差值在0~2之間.
 
  這樣我們就可以在編碼/解碼前對Output參數的TMemoryStream事先設置緩沖區大小了....
 
 uses
  encddecdEx; 
 var
  Input,Output:TMemoryStream;
 begin
  Input:=TMemoryStream.Create;
  try
   Input.LoadFromFile('c:\aaa.txt');
   Output:=TMemoryStream.Create;
   try
    Output.Size:=(Input.Size + 2) div 3 * 4;
    EncodeStream(Input,Output);
   finally
    Output.Free;
   end;
  finally
   Input.Free;
  end;
 end;
 
 OK! 大功告成!!! 大家有興趣可以測試一下,加不加Output.Size:=(Input.Size + 2) div 3 * 4這一句的不同效果~
 
二.ZLib壓縮/解壓
 
  在一些分布式系統中,特別是Internet分布式系統,由於網絡帶寬所限,我們需要對傳輸的大流量數據進行壓縮,以減輕網絡的負擔,加快程序運行速度,一般用到的壓縮/解壓方法是使用ZLib單元. ZLib單元主要提供了兩個類:TCompressionStream和TDeCompressionStream. 這兩個類分別處理壓縮和解壓縮. 使用方法可以查閱相關的資料. 在這里提供兩個過程,再對壓縮時的參數作些比較:
 
uses
 ZLib;
 
procedure Zip(Input,Output:TStream;Compress:Boolean);
const
 MAXBUFSIZE=1024 * 16;  //16 KB
var
 CS:TCompressionStream;
 DS:TDecompressionStream;
 Buf:array[0..MAXBUFSIZE-1] of Byte;
 BufSize:Integer;
begin
  if Assigned(Input) and Assigned(Output) then
 begin
  if Compress then
  begin
   CS:=TCompressionStream.Create(clDefault,Output);
   try
    CS.CopyFrom(Input,0); //從開始處復制
   finally
    CS.Free;
   end;
  end else
  begin
   DS:=TDecompressionStream.Create(Input);
   try
    BufSize:=DS.Read(Buf,MAXBUFSIZE);
    while BufSize>0 do
    begin
     Output.Write(Buf,BufSize);
     BufSize:=DS.Read(Buf,MAXBUFSIZE);
    end;
   finally
    DS.Free;
   end;
  end;
 end;
end;
 
function Zip(Input:string;Compress:Boolean):string;
var
 InputStream,OutputStream:TStringStream;
begin
 if Input='' then Exit;
 InputStream:=TStringStream.Create(Input);
 try
  OutputStream:=TStringStream.Create('');
  try
   Zip(InputStream,OutputStream,Compress);
   Result:=OutputStream.DataString;
  finally
   OutputStream.Free;
  end;
 finally
  InputStream.Free;
 end;
end;
 
  以上兩個方法是兩個名稱一樣,參數不同的過程. 第一個是對流進行壓縮/解壓,Input,Output分別是壓縮/解壓前的流與壓縮/解壓后的流. 第二個是對字符串進行壓縮/解壓. 兩個過程都有Compress參數,這個參數用來決定進行壓縮操作還是解壓操作: True--壓縮; false--解壓.
 
  在第一個過程中,有這樣一句:
  CS:=TCompressionStream.Create(clDefault,Output);
  這是在建立壓縮類以對流進行壓縮, 這里面有個參數clDefault,當然還有其它的選項:clNone, clFastest, clDefault, clMax;
clNone與clFastest就不討論了,因為不能獲得良好的壓縮效果,在這里想討論clDeafult與clMax哪一個好點,我作了一些測試,測試數據如下:
 

        源文件大小  壓縮所用時間   壓縮后文件大小
 clDefault   2.71M     ~1.4s      ~937K
         5.10M     ~2.8s      ~1.77M
 
 clMax     2.71M     ~2.5s      ~934K
         5.10M     ~4.7s      ~1.77M
 
  由這些數據可以看出,clDefault參數與clMax參數,壓縮率已經非常接近了,但是所用的時間卻相差了近一倍! 也就是說,差不多的壓縮效率,clDefault參數比clMax參數節省了一半的時間! 因此,建議大家使用參數clDefault,這是壓縮效率比較好的參數.
 

三. 何對MIDAS封包進行壓縮.
 
  我們知道,MIDAS封包外在類型是OleVariant,其內部格式沒有對外公開! 經過我的一些測試,該封包是以varByte為基礎類型的VarArray數組. 
因此,我們可以將其轉換成string類型再進行壓縮,至於壓縮后是以string傳輸還是轉換回VarByte array類型,就由個人決定了. 下面的函數完成將MIDAS數據包轉換成string類型.
 
function UnpackMIDAS(vData:OleVariant):string;
var
 P:Pointer;
 Size:Integer;
begin
 if not VarIsArray(vData) then Exit;
 Size:=VarArrayHighBound(vData,1)-VarArrayLowBound(vData,1)+1;
 P:=VarArrayLock(vData);
 try
  SetLength(Result,Size);
  Move(P^,Result[1],Size);
 finally
  VarArrayUnLock(vData);
 end;
end;
 
假設以下為MIDAS服務器或COM+對象一個方法.
 
function TDeptCoor.GetDeptData: OleVariant;
var
 Command:WideString;
 Options:TGetRecordOptions;
 RecsOut:Integer;
 Params,OwnerData:OleVariant;
begin
 try
  Command:='SELECT DeptID,DeptNo,DeptName,MasterID FROM Department ORDER BY DeptNo';
  Options:=[grReset,grMetaData];
  Result:=FCommTDM.AS_GetRecords('CommDsp',-1,RecsOut,Byte(Options),Command,Params,OwnerData);
  Result:=UnpackMIDAS(Result);  //將MIDAS封包轉換成string類型
  Result:=Zip(Result,True);      //進行壓縮,再將壓縮后結果轉回. 
  SetComplete;
 except
  SetAbort;
  raise;
 end;
end;
 
客戶端只要壓壓縮后就可以使用了:
 
procedure TForm1.Button1Click(sender:TObject);
var
 vData:string;
begin
 vData:=DeptCoor.GetDeptData;
 vData:=Zip(vData,False);     //解壓
 ClientDataSet1.XMLData:=vData;  //注意,這里用的是XMLData,不是Data,否則會報錯!!!
end;
 
         
四. SOAP系統中壓縮后編碼:
 
 在SOAP系統中,由於二進制數據不能直接傳遞,需要進行Base64編碼, 我們可以在數據傳遞前先壓縮/Base64編碼,接收后再Base64解碼/解壓縮.
同樣,也給出兩個函數,來分別完成這兩個過程
 
function SoapPacket(const Input:string):string;  
var
 InputStream,OutputStream:TStringStream;
begin
 InputStream:=TStringStream.Create(Input);
 try
  OutputStream:=TStringStream.Create('');
  try
   Zip(InputStream,OutputStream,True);
   InputStream.Size:=0;
   OutputStream.Position:=0;  //很重要!!!
   EncodeStream(OutputStream,InputStream);
   Result:=InputStream.DataString;
  finally
   OutputStream.Free;
  end;
 finally
  InputStream.Free;
 end;
end;
 
function SoapUnpack(const Input:string):string;
var
 InputStream,OutputStream:TStringStream;
begin
 InputStream:=TStringStream.Create(Input);
 try
  OutputStream:=TStringStream.Create('');
  try
   DecodeStream(InputStream,OutputStream);
   InputStream.Size:=0;
   OutputStream.Position:=0; //很重要!!!
   Zip(OutputStream,InputStream,False);
   Result:=InputStream.DataString;
  finally
   OutputStream.Free;
  end;
 finally
  InputStream.Free;
 end;
end; 
 
http://www.cnblogs.com/wxy8/archive/2011/06/16/2082961.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM