最近在寫的程序與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之間.
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;
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;
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;
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;
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哪一個好點,我作了一些測試,測試數據如下:
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
5.10M ~4.7s ~1.77M
由這些數據可以看出,clDefault參數與clMax參數,壓縮率已經非常接近了,但是所用的時間卻相差了近一倍! 也就是說,差不多的壓縮效率,clDefault參數比clMax參數節省了一半的時間! 因此,建議大家使用參數clDefault,這是壓縮效率比較好的參數.
三. 何對MIDAS封包進行壓縮.
我們知道,MIDAS封包外在類型是OleVariant,其內部格式沒有對外公開! 經過我的一些測試,該封包是以varByte為基礎類型的VarArray數組.
因此,我們可以將其轉換成string類型再進行壓縮,至於壓縮后是以string傳輸還是轉換回VarByte array類型,就由個人決定了. 下面的函數完成將MIDAS數據包轉換成string類型.
因此,我們可以將其轉換成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;
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;
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;
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;
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;
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
