Tcompression与TDeCompressionStream应用(文件分割)

本文介绍了使用Tcompression和TDeCompressionStream在Delphi中实现文件分割存储到Oracle数据库的过程,包括如何压缩和解压缩文件流,以及如何通过小步迭代开发逐步完善该功能。通过使用压缩流减少了数据库存储负担,并实现了文件的分割以提高性能。

KeyLife富翁笔记

作者 : liceblone
标题 : Tcompression与TDeCompressionStream应用(文件分割)
关键字: Tcompression与TDeCompressionStream应用(文件分割)
分类 : 个人专区
密级 : 公开

(评分: , 回复: 0, 阅读: 1433) »»

Tcompression与TDeCompressionStream应用(文件分割)
关键字:
分类 : 个人专区
密级 : 公开
(评分: , 回复: 0, 阅读: 152) »»
KeyLife富翁笔记  
作者 : zgl198171
标题 : 项目迭代开发手记--文件分割存储用例的实现过程
关键字:
分类 : 个人专区
密级 : 公开
(评分: , 回复: 0, 阅读: 1027) »»
项目迭代开发手记--文件分割存储用例的实现过程(1)     选择自 haozi 的 Blog  
关键字   项目迭代开发手记--文件分割存储用例的实现过程(1)
出处    
摘  要 本文详细描述了在尝试使用极限编程在软件开发中的一个实现例子,在小步迭代开发中逐步的实现用例需要的功能,同时每个迭代都能集成实现的功能。
关键字 极限编程
在一次项目开发中我们开发组遇到了一个需求,该需求的要使用的技术是我们所不熟悉的,就是说在开发过程中我们必须逐步的掌握该项技术,同时希望能够尽可能的保持代码有好的结构,因为在不断增加功能的过程中,会让代码逐渐的变得复杂降低可阅读性。
该需求的用例很简单,就是把图档文件保存到数据库中,同时在需要时提取出来显示。但是有的图档文件会比较大,在向数据库提交过程中可能会有性能问题,还有图档文件格式的问题,(.bmp,.Jpeg)
小组讨论的时,大家提出了首先要对要保存的数据进行压缩,同时对较大的文件要分割成合适的块,这样提交到数据库中才不会有性能问题。这样提取图档文件过程中就得对分割压缩的数据进行解压和拼接才能获得原始数据,还有就是文件格式的转换问题。
该项功能我想对于熟悉的开发人员来说可能很简单,但是由于我们组的开发人员没有类似的开发经验,就不可能在一开始获得优良的设计,只能在不断的开发进程中改进你的设计,在以往的开发中我们尝试过小步迭代开发的好处,就是在不断累积中实现需求的功功能,同时减少过程中的挫折感——你的每一步都很好的实现了需求的功能,还能每日集成你的软件,实时掌握你的开发进度。
每次迭代实现认为优先级高的功能。
我们认为图档文件的保存和提取在开发中是优先级最高的,我们首先保证可以向数据库提交图档,同时可以提起图档。这样在我们完成这个功能后其它开发组就可以使用这项功能了(虽然会有因为图片太大保存和提取的效率问题,但是它是可以使用的功能了)。我们使用的开发工具是Delphi数据库是Oracle
迭代1:文件以二进制的形式保存到数据库中,然后再以二进制的形式从数据库中提取出来。
       通过查找资料后我们决定Oracle数据库使用Long Raw 类型的字段来保存二进制数据,Delphi里面使用TBlobField来把流装载都字段中向数据库提交。本文的例子简化了表的设计和使用简化后的代码
表的设计
字段名
字段类型
字段长度
字段说明
FID
Number
主键
F_NAME
VarChar2
50
文件名称
F_BINARY_DATA
Long Raw
二进制图档数据
procedure TForm1.Button2Click(Sender: TObject);
var
  OpenDialog: TOpenDialog;
  lFileFullName: string;
  lBlobStream: TMemoryStream;
begin
  lFileFullName := '';    
  OpenDialog := TOpenDialog.Create(Self);
  lBlobStream := TMemoryStream.Create;
  try
    OpenDialog.InitialDir := extractfilepath(Application.ExeName);
    if OpenDialog.Execute then
      lFileFullName := OpenDialog.FileName;
    if lFileFullName <> '' then
    begin
      lBlobStream.LoadFromFile(lFileFullName);
      ClientDataSet1.Append;
      ClientDataSet1.FieldByName('F_ID').Value := 2;
      ClientDataSet1.FieldByName('F_NAME').Value := lFileFullName;
      (ClientDataSet1.FieldByName('F_BINARY_DATA')
as TBlobField).LoadFromStream(lBlobStream);
      ClientDataSet1.Post;
    end;
  finally
    OpenDialog.Free;
    lBlobStream.Free;
  end;
end;
这样就可以在向数据库提交图档数据了,这里图档文件先装载成流然后以二进制流的形式提交到数据库中的。
提取的方式就是
(ClientDataSet1.FieldByName('F_BINARY_DATA')
as TBlobField). SaveToStream(lBlobStream);
这样就完成图档文件的保存和提取功能。由于对该项技术不熟悉实现该功能花了我们3小时时间。在下午下班的时候我们提交了可以使用的保存图档文件的程序。
项目迭代开发手记--文件分割存储用例的实现过程(2)
在第二天早上的开发中我们扩展了第一天迭代开发的用例,我们考虑增加了压缩流的功能,减少数据的存储负担。
迭代2:
用例:对向数据库提交的二进制流进行压缩;那么从数据库提取的时候要进行解压操作,以获得原始图档数据。
通过查询资料我们找到Delphi的ZLib库支持对字节流的压缩和解压缩功能,这样我们只要使用该类的TCompressionStream和TDecompressionStream就可以实现压缩和解压缩功能,为了更好的封装这种过程方便项目组重用,我们对压缩和解压缩功能进行了封装。
我们定义了TLoadBinaryDataToDB类来封装压缩和解压的功能,以便于使用。
类定义了procedure LoadFile(fileFullName: string);来装载图档文件,同时压缩装载的流。
procedure TLoadBinaryDataToDB.LoadFile(fileFullName: string);
begin
  FStream.LoadFromFile(fileFullName);
  CompressStream; //对装载的流进行压缩
end;
procedure TLoadBinaryDataToDB.CompressStream;
var
  iSize: Integer;
  lDestStream : TMemoryStream;
  lCompressionStream : TCompressionStream;
begin
  lDestStream := TMemoryStream.Create;
  lCompressionStream := TCompressionStream.Create(clMax,lDestStream);
  try
  iSize := FStream.Size;  //获得图像流的原始尺寸
  FStream.SaveToStream(lCompressionStream); //将原始图像流进行压缩,
//lDestStream中保存着压缩后的图像流
  lCompressionStream.Free;
  FStream.Clear;
  FStream.WriteBuffer(iSize, SizeOf(iSize));
  FStream.CopyFrom(lDestStream, 0);//写入经过压缩的图像流
  finally
    lDestStream.Free
  end;
end;
这样装载后的流就被压缩了,向数据库提交的流就是进过压缩过的。

LoadBinaryDataToDB.LoadFile(lFullFileName);
  ClientDataSet1.Append;
  ClientDataSet1.FieldByName('F_ID').Value := 2;
  ClientDataSet1.FieldByName('F_NAME').Value := lFileFullName;
  (ClientDataSet1.FieldByName('F_BINARY_DATA')
as TBlobField).LoadFromStream(LoadBinaryDataToDB.FileStream);
  ClientDataSet1.Post;
类还封装了另一个函数 procedure UnCompressStream(var stream :TMemoryStream)该函数实现了对压缩流的逆向解压过程。
procedure TLoadBinaryDataToDB.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;
通过下面这样的调用我们就可以还原图档数据。
(ClientDataSet1.FieldByName('F_BINARY_DATA')
as TBlobField). SaveToStream(lBlobStream);
LoadBinaryDataToDB.UnCompressStream(lBlobStream);
//lBlobStream 为解压后的二进制字节流了
对了类进行这样的封装很好的实现了压缩和解压代码的重用,开发人员只要创建该类的对象就可以对流进行相应的处理。
到中午的时候我们完成了该类的实现,提交了一个可以使用的版本,由于对该项技术的不熟悉过程中我们还是走了不少弯路,查找了不少技术文档。
项目迭代开发手记--文件分割存储用例的实现过程(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完成的时候我们实现了对了流的分割压缩,文件分割存储用例到这里获得一个好的解决方案,通过小步的迭代前进我们可以在每一次迭代结束的时候获得可以使用的功能代码,剩下来就使考虑图档文件的格式问题了。其实更主要的通过这次开发我们让新加入的组员获得了一次很好的编程培训,更容易理解要实现一个功能的具体思路和步骤。

2006-11-9 22:04:57   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值