也谈"对MemoryStream的一些改进"

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下最以人为本的开发工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值