項目迭代開發手記--文件分割存儲用例的實現過程(3)
上午的迭代2完成後,我們獲得了一個有完整壓縮流功能的實現代碼,這次迭代完成的代碼是可用的,我們在迭代2中完成了我們既定的任務。在下午的小組討論中,我們繼續考慮下一階段的迭代目標,由於沒有決定圖檔文件的格式,我們決定先不考慮圖片格式的問題,先實現文件的分割功能。文件的分割主要是考慮當圖檔文件太大的時,數據庫提交性能會變得非常慢,分割的目的就是改進提交的性能。
迭代3:
對向數據庫提交的二進制流進行分割壓縮;那麼從數據庫提取的時候要進行解壓和拼接操作,以獲得原始圖檔數據。
在分割功能的設計和編碼前,我們重新審視了上午的代碼——那個壓縮類TLoadBinaryDataToDB,發覺該類似乎職責太多,它要負責把文件裝載成流,然後才對流進行壓縮和解壓縮,我們發現UnCompressStream函數有更好的通用性,只要是壓縮的流就可以對其進行解壓。而壓縮功能在這個類裡似乎只能對通過文件裝載的流進行壓縮,如果流是以另一種形式獲得的,不是以文件裝載的形式,那麼我們不知道該如何對該流進行壓縮。這裡似乎違背了功能單一的職責,類既負責了流的裝載,又負責流的壓縮;於是我們對該類進行了重構已獲得結構更好的的類,以增加類的重用性。
重構後的類只有兩個公用的方法 CompressStream 和 UnCompressStream 它們都已流為參數,通過對傳入流的處理來實現壓縮和解壓縮功能。
procedure TCompressStream.CompressStream(var stream: TMemoryStream);
var
iSize: Integer;
lDestStream: TMemoryStream;
lCompressionStream: TCompressionStream;
begin
lDestStream := TMemoryStream.Create;
lCompressionStream := TCompressionStream.Create(clMax, lDestStream);
try
iSize := stream.Size; //獲得圖像流的原始尺寸
stream.SaveToStream(lCompressionStream); //將原始圖像流進行壓縮,
// lDestStream中保存著壓縮後的圖像流
lCompressionStream.Free;
stream.Clear;
stream.WriteBuffer(iSize, SizeOf(iSize)); //寫入原始圖像的尺寸
stream.CopyFrom(lDestStream, 0); //寫入經過壓縮的圖像流
finally
lDestStream.Free
end;
end;
解壓縮函數
procedure TCompressStream.UnCompressStream(var stream: TMemoryStream);
var
DecompressionStream: TDecompressionStream;
Buffer: PChar;
Count: Integer;
begin
stream.ReadBuffer(Count, SizeOf(Count));
GetMem(Buffer, Count); //根據圖像尺寸大小為將要讀入的原始圖像流分配內存塊
DecompressionStream := TDecompressionStream.Create(stream);
try
DecompressionStream.ReadBuffer(Buffer^, Count); //將被壓縮的圖像流解壓縮,
//然後存入 Buffer內存塊中
stream.Clear;
stream.WriteBuffer(Buffer^, Count); //將原始圖像流保存至 stream流中
stream.Position := 0;
finally
FreeMem(Buffer); // 釋放內存
end;
end;
經過重構後,類TCompressStream無疑提高了重用性,同時有更好的結構。除去了把文件裝載成流的功能後,TCompressStream職責變得更單一了。它對已任何形式獲得得的流都可以進行壓縮和解壓縮。完成TLoadBinaryDataToDB重構我們開始考慮對流進行分割功能的實現。
在假定一個流被分割成5份,那麼拼接時就要有一個順序我們考慮在數據庫增加一個順序的字段來保存流各個塊之間的分割順序。
字段名
字段類型
字段長度
字段說明
FID
Number
主鍵
F_NAME
VarChar2
50
文件名稱
F_SERIAL
Number
文件分割順序號
F_BINARY_DATA
Long Row
二進制數據
同樣我們考慮把這個功能封裝在一個類裡面。我們實現了一個叫TStreamIncise的類,在設計這個類時,我們為了更好的增加對這類要設計成什麼樣子進行了很好的討論,首先我們模擬了如何使用該類。
for I := 0 to IncisedCount - 1 do
begin
StreamIncise.GetInciseStream(lStream); //獲得分割流
ClientDataSet2.Append;
ClientDataSet2.FieldByName('F_ID').Value := I; //取序列號
ClientDataSet2.FieldByName('F_NAME').Value := FFileFullName;
ClientDataSet2.FieldByName('F_SERIAL').Value := I; // 取每次分割的序列號
lCompressionStream.CompressStream(lStream);
(ClientDataSet2.FieldByName('F_BINARY_DATA')
as TBlobField).LoadFromStream(lStream);
ClientDataSet2.Post;
end;
我們用代碼估計了類的調用方式,通過這樣的模擬代碼我們獲得了以下信息
1) 要獲得文件的被分割數,就是說如果使用上面的模擬代碼,我們必須先獲得流的分割數。
2) TStreamIncise流在執行前先獲得要處理流,同時設定分割塊的大小。
我們用FInciseSize 來保存分割快的大小值,FStreamSize 保存流的大小值,FRemainSize保存每次分割後的剩余值。FInciseSize 在初始化函數 Create 中初始化。
FInciseSize := 50000; //設置分割的大小
LoadFromStream 把原始的流裝載過來。
procedure TStreamIncise.LoadFromStream(stream: TMemoryStream);
begin
FMemoryStream := stream; // 保存一個流的引用
FStreamSize := stream.Size;
FRemainSize := FStreamSize;
end;
GetIncisedCount 獲得裝載的原始流要被分割的數量。
function TStreamIncise.GetIncisedCount: Integer;
begin
Result := FStreamSize div FInciseSize + 1;
end;
SetStreamDefault 用來把獲得流設置到初始位置。
procedure TStreamIncise.SetStreamDefault;
begin
if Assigned(FMemoryStream) then FMemoryStream.Position :=0;
end;
核心的函數是GetInciseStream 通過調用它用戶獲得分割好後的流。
procedure TStreamIncise.GetInciseStream(inciseStream: TMemoryStream);
var
iMaxError: Integer;
Count: Integer;
Buffer: PChar;
begin
Count := GetBufferCount;
GetMem(Buffer, Count);
try
FMemoryStream.ReadBuffer(Buffer^, Count);
InciseStream.Clear;
inciseStream.WriteBuffer(Buffer^, Count);
InciseStream.Position := 0;
FRemainSize := FRemainSize - Count;
finally
FreeMem(Buffer);
end;
end;
這裡GetBufferCount 每次返回分割塊的大小,當剩余的流大小不夠5000 時它返回剩下流的長度。
function TStreamIncise.GetBufferCount: Integer;
begin
Result := FInciseSize;
if FRemainSize < FInciseSize then
Result := FRemainSize;
end;
最終我們獲得了一個可以這樣調用的分割類:
procedure TForm1.Button8Click(Sender: TObject);
var
StreamIncise: TStreamIncise;
I: Integer;
lStream: TMemoryStream;
lCompressionStream: TCompressStream;
begin
StreamIncise := TStreamIncise.Create;
lStream := TMemoryStream.Create;
lCompressionStream := TCompressStream.Create;
StreamIncise.LoadFromStream(FStream);
StreamIncise.SetStreamDefault;
try
for I := 0 to StreamIncise.IncisedCount - 1 do
begin
StreamIncise.GetInciseStream(lStream); //獲得分割流
ClientDataSet2.Append;
ClientDataSet2.FieldByName('F_ID').Value := I; //取序列號
ClientDataSet2.FieldByName('F_NAME').Value := FFileFullName;
ClientDataSet2.FieldByName('F_SERIAL').Value := I; // 取每次分割的序列號
lCompressionStream.CompressStream(lStream);
(ClientDataSet2.FieldByName('F_BINARY_DATA')
as TBlobField).LoadFromStream(lStream);
ClientDataSet2.Post;
end;
finally
StreamIncise.Free;
lStream.Free;
lCompressionStream.Free;
end;
end;
最後我們增加了InciseSize 屬性,讓程序員在創建類以後可以自己修改分割塊的大小。
通過這樣的調用,我們就可以把分割類具體的保存業務的耦合解開,從而增加了分割類下次被重用的可能性。在查閱資料過程中我們也找到一些分割的例子,只是都跟具體的業務耦合得很緊密,要重用該代碼除了粘貼復制以外基本上沒有他法。
這樣當迭代3完成的時候我們實現了對了流的分割壓縮,文件分割存儲用例到這裡獲得一個好的解決方案,通過小步的迭代前進我們可以在每一次迭代結束的時候獲得可以使用的功能代碼,剩下來就使考慮圖檔文件的格式問題了。其實更主要的通過這次開發我們讓新加入的組員獲得了一次很好的編程培訓,更容易理解要實現一個功能的具體思路和步驟。