事情的起因还是那个破烂电子相册软件,今天又发现了一个可改进之处,有一段程序我原来是这么写的: 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追加到一个文件中的任务,我使用了另一个TMemoryStream,让它先打开文件,然后使用CopyFrom()函数,从原始Stream中加入数据,最后再保存到文件中。 其中最糟糕的就是CopyFrom()函数,它会开辟一块新的内存,先调用ReadBuffer()函数,从源Stream中取得数据,再调用自身的WriteBuffer()函数,写到自身的Buffer中,最后再释放这块临时内存,这些过程可以看这段代码: function TStream.CopyFrom(Source: TStream; Count: Int64): Int64; const MaxBufSize = $F000; var BufSize, N: Integer; Buffer: PChar; begin if Count = 0 then begin Source.Position := 0; Count := Source.Size; end; Result := Count; if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count; GetMem(Buffer, BufSize); try while Count <> 0 do begin if Count > BufSize then N := BufSize else N := Count; Source.ReadBuffer(Buffer^, N); WriteBuffer(Buffer^, N); Dec(Count, N); end; finally FreeMem(Buffer, BufSize); end; end; 而且,不知道为何,Delphi自己提供的Move()函数在内存拷贝时显得特别的慢。最后导致的结果就是,我在将30MB左右的数据写入文件时,会花半分钟的时间。
知道了问题所在,那么要加速这个过程就很简单了,首先当然要避免内存拷贝,所以我决心去掉那个累赘的FileStream,让原始Stream自己将内存数据写入到文件,那样不是就可以了吗? 但是无论是TMemoryStream,还是TFileStream,都只提供将数据完全写入一个文件的功能,而我需要的则是追加功能,呵呵,这个简单,自己打开文件,然后WriteFile()就可以了,所以最终的解决方法就是: 从TMemoryStream继承出一个新类,暂且叫做TMemoryStreamEx,加入一个新的方法,叫做:AppendToFile(),可以将内存数据完全追加到已存在的文件内,函数内容如下: 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;
好了,替换掉原来的那段程序,新的程序变为: procedure TExeExporter.CreateExecutableFile(const AFileName:String;const AStream:TMemoryStreamEx); begin AStream.AppendToFile(AFileName); end; 就那么简单,速度也缩短到仅仅2-3秒了。