http://phynex.blog.163.com/blog/static/1590873200942185632551/
转自http://i.92wy.com/space.php?uid=756996&do=blog&id=91917
经(phynex)测试,方法相当好用,速度草快,14+M大小的流,copy只需要320ms左右时间。
也谈"对MemoryStream的一些改进"
2007-08-17 10:40
"对TMemoryStream的一些改进" 一文的出处来自于tonylk的Blog,并发布于csdn文档中心。 原文地址: http://dev.csdn.net/develop/article/55/55934.shtm 问题起源很简单,我们要打开一个存在的文件,然后在这个文件的未尾处追加一个TMemoryStream到文件中,并保存这个文件。(详情请点击上面的链接查看原文) 文章中原先使用的函数,将30MB左右的数据写入文件时,会花半分钟的时间。 procedure CreateFile(const AFileName:String;const AStream:TMemoryStream); var FileStream:TMemoryStream; begin ShowProgressForm(nil); FileStream:=TMemoryStream.Create(); try FileStream.LoadFromFile(AFileName); FileStream.Position:=FileStream.Size; AStream.Position:=0; FileStream.CopyFrom(AStream,AStream.Size); FileStream.SaveToFile(AFileName); finally FileStream.Free; end; end; 文章中后来提到的解决方案:从TMemoryStream继承一个新类,加入一新的方法,这样速度也缩短到仅仅2-3秒了 procedure TMemoryStreamEx.AppendToFile(const AFileName:String); var FileHandle:LongWord; CurPos:LongWord; BytesWritten:LongWord; begin FileHandle:=CreateFile(PChar(AFileName),GENERIC_WRITE,0,nil,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,0); if FileHandle=INVALID_HANDLE_VALUE then begin raise MemoryStreamExException.Create('Error when create file'); end; try CurPos:=SetFilePointer(FileHandle,0,nil,FILE_END); LockFile(FileHandle,CurPos,0,Size,0); try BytesWritten:=0; if not WriteFile(FileHandle,Memory^,Size,BytesWritten,nil) then begin raise MemoryStreamExException.Create('Error when write file'); end; if (Size<>BytesWritten) then begin raise MemoryStreamExException.Create('Wrong written size'); end; finally UnlockFile(FileHandle,CurPos,0,Size,0); end; finally CloseHandle(FileHandle); end; end; tonylk提供了一个非常好的思路,利用WriteFile这个API函数巧妙地避用了TStream.CopyFrom这个比较缓慢的成员函数,达到了优化速度的目的。可是,我们有没有更加简洁的方法,更优雅的实现这个功能呢?让我们接下来试试吧! 就像我前几天分析Delphi四舍五入问题一样,我总有预感,Borland那群伟大的工程师们能够设计出如此优雅简洁的Delphi,他们一定对于这样的问题有所考虑,即使没有现成的类也有可能只需要简单的派生某个类就可以解决这个问题,而且对于WriteFile这个函数,我们应该有种似曾相识的感觉吧。 先来看看TStream类的实现,我们注意到,它的Read,Write方法都是抽象虚方法,声明如下: function Read(var Buffer; Count: Longint): Longint; virtual; abstract; function Write(const Buffer; Count: Longint): Longint; virtual; abstract; 也就是说,派生类必须对其进行实现,那么我们来看看以Read为例,来看看派生类们是如何实现的吧? 打开Classes单元,发现了三个TStream类的派生类,分别是TMemoryStream,TStringStream,TFileStream,下面分别是它们对Read方法的实现。 function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move(Pointer(Longint(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; function TStringStream.Read(var Buffer; Count: Longint): Longint; begin Result := Length(FDataString) - FPosition; if Result > Count then Result := Count; Move(PChar(@FDataString[FPosition + 1])^, Buffer, Result); Inc(FPosition, Result); end; function THandleStream.Read(var Buffer; Count: Longint): Longint; //THandleStream是TFileStream的父类 begin Result := FileRead(FHandle, Buffer, Count); if Result = -1 then Result := 0; end; 等等!! THandleStream中调用是FileRead,再看看前面我们那似曾相似的API--WriteFile,嗯,有点关系吧?我们接着看FileRead的实现: function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer; begin if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then Result := -1; end; 我们猜测似乎越来越近的,再看看它的THandleStream的Write方法吧。 function THandleStream.Write(const Buffer; Count: Longint): Longint; begin Result := FileWrite(FHandle, Buffer, Count); if Result = -1 then Result := 0; end; FileWrite的实现代码 function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer; begin if not WriteFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then Result := -1; end; 找到了!答案就在TFileStream里了,我们调用Write方法应该可以达到相同的效果,让我们看看构造函数吧。 constructor TFileStream.Create(const FileName: string; Mode: Word; Rights: Cardinal); //Rights在Linux下才有用,Windows忽略此参数,为零。 begin if Mode = fmCreate then begin inherited Create(FileCreate(FileName, Rights)); if FHandle < 0 then raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]); end else begin inherited Create(FileOpen(FileName, Mode)); if FHandle < 0 then raise EFOpenError.CreateResFmt(@SFOpenErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]); end; end; 我们再看FileCreate,FileOpen的源码: function FileCreate(const FileName: string): Integer; begin Result := Integer(CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)); //CREATE_ALWAYS表示如果文件存在,创建新文件覆盖它 end; function FileOpen(const FileName: string; Mode: LongWord): Integer; const AccessMode: array[0..2] of LongWord = ( GENERIC_READ, GENERIC_WRITE, GENERIC_READ or GENERIC_WRITE); ShareMode: array[0..4] of LongWord = ( 0, 0, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE); begin Result := -1; if ((Mode and 3) <= fmOpenReadWrite) and ((Mode and $F0) <= fmShareDenyNone) then Result := Integer(CreateFile(PChar(FileName), AccessMode[Mode and 3], ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); end; 两个函数归根结底都是调用CreateFile这个函数,只是参数不一样罢了。根据两个函数的分析,使用FileOpen就对了。 经过这么多步的分析,答案已经揭晓了,使用TFileStream对象来打开文件,并调用Write方法追加TMemoryStream对象的数据到文件中。好,下面贴代码咯: procedure CreateFileEx(const AFileName:String;const AStream:TMemoryStream); var FileStream:TFileStream; begin FileStream:=TFileStream.Create(AFileName, fmOpenWrite); //不能是fmCreate,要不然就会调用FileCreate了 try FileStream.Position:=FileStream.Size;//指针移到文件末尾 AStream.Position:=0; FileStream.Write(AStream.Memory^, AStream.Size); //追加数据 finally FileStream.Free; //关闭文件 end; end; 上面这段程序再平常不过了,我们忍不住会惊叹,Borland为我们提供了如此优雅的解决方案,让我们避开了烦人的API函数,要做的仅仅是调用某个类的公用方法就可以了。 再次证明,Delphi无疑是Windows下最以人为本的开发工具。 |