CHAPT20
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第二十章开发Delphi对象式数据管理功能
面向对象技术是九十年代的主流技术,各类应用软件如果以面向对象的方法构造并且渗透面向对象的风格将使软件具有更高的品质。
在面向对象程序设计中,对象式数据管理占有很重要的地位。
在Delphi中,对对象式数据管理的支持方式是其一大特色。
Delphi是一个面向对象的可视化设计与面向对象的语言相结合的集成开发环境。
Delphi 的核心是部件。
部件是对象的一种。
Delphi应用程序完全是由部件来构造的,因此开发高性能的Delphi应用程序必然会涉及对象式数据管理技术。
对象式数据管理包括两方面的内容:
●用对象来管理数据
●对各类数据对象(包括对象和部件)的管理
Delphi在这两方面都做的相当出色。
在Delphi的早期版本Turbo Pascal 中就曾有流(Stream)、群(Collection)和资源(Resource)等专门用于对象式数据管理的类。
在Delphi中,这些功能得到了大大的加强。
Delphi将对象式数据管理类归结为Stream对象(Stream)和Filer对象(Filer),并将它们应用于可视部件类库(VCL)的方方面面。
它们不仅提供了在内存、外存和Windows资源中管理对象的功能,还提供了在数据库BLOB字段中对象的功能。
在本章中将介绍Stream对象和Filer对象的实现原理、应用方法以及在超媒体系统中的应用。
这对于运用Delphi 开发高级应用是很重要的。
20.1 流式对象的实现原理和应用
Stream对象,又称流式对象,是TStream、THandleStream、TFileStream、TMemoryStream、TResourceStream和TBlobStream等的统称。
它们分别代表了在各种媒介上存储数据的能力,它们将各种数据类型(包括对象和部件) 在内存、外存和数据库字段中的管理操作抽象为对象方法,并且充分利用了面向对象技术的优点,应用程序可以相当容易地在各种Stream对象中拷贝数据。
下面介绍各种对象的数据和方法及使用方法。
20.1.1 TStream对象
TStream对象是能在各种媒介中存储二进制数据的对象的抽象对象。
从TStream 对象继承的对象用于在内存、Windows资源文件、磁盘文件和数据库字段等媒介中存储数据。
TStream中定义了两个属性:Size和Position。
它们分别以字节为单位表示的流的大小和当前指针位置。
TStream中定义的方法用于在各种流中读、写和相互拷贝二进制数据。
因为所有的Stream对象都是从TStream中继承来的,所以在TStream中定义的域和方法都能被Stream对象调用和访问。
此外,又由于面向对象技术的动态联编功能,TStream为各种流的应用提供了统一的接口,简化了流的使用;不同Stream对象是抽象了对不同存储媒介的数据上的操作,因此,TStream的需方法为在不同媒介间的数据拷贝提供了最简捷的手段。
20.1.1.1 TStream的属性和方法
1. Position属性
声明:property Position: Longint;
Position属性指明流中读写的当前偏移量。
2. Size属性
声明:property Size: Longint;
Size属性指明了以字节为单位的流的的大小,它是只读的。
3. CopyFrom方法
声明:function CopyFrom(Source: TStream; Count: Longint): Longint;
CopyFrom从Source所指定的流中拷贝Count个字节到当前流中,并将指针从当前位置移动Count个字节数,函数返回值是实际拷贝的字节数。
4. Read方法
声明:function Read(var Buffer; Count: Longint): Longint; virtual; abstract;
Read方法从当前流中的当前位置起将Count个字节的内容复制到Buffer中,并把当前指针向后移动Count个字节数,函数返回值是实际读的字节数。
如果返回值小于Count,这意味着读操作在读满所需字节数前指针已经到达了流的尾部。
Read方法是抽象方法。
每个后继Stream对象都要根据自己特有的有关特定存储媒介的读操作覆盖该方法。
而且流的所有其它的读数据的方法(如:ReadBuffer,ReadComponent等)在完成实际的读操作时都调用了Read方法。
面向对象的动态联编的优点就体现在这儿。
因为后继Stream对象只需覆盖Read方法,而其它读操作(如ReadBuffer、ReadComponent等)都不需要重新定义,而且TStream还提供了统一的接口。
5. ReadBuffer方法
声明:procedure ReadBuffer(var Buffer; Count: Longint);
ReadBuffer方法从流中将Count个字节复制到Buffer 中,并将流的当前指针向后移动Count个字节。
如读操作超过流的尾部,ReadBuffer方法引起EReadError异常事件。
6. ReadComponent方法
声明:function ReadComponent(Instance: TComponent): TComponent;
ReadComponent方法从当前流中读取由Instance所指定的部件,函数返回所读的部件。
ReadComponent在读Instance及其拥有的所有对象时创建了一个Reader对象并调用它的ReadRootComponent方法。
如果Instance为nil,ReadComponent的方法基于流中描述的部件类型信息创建部件,并返回新创建的部件。
7. ReadComponentRes方法
声明:function ReadComponentRes(Instance: TComponent): TComponent;
ReadComponentRes方法从流中读取Instance指定的部件,但是流的当前位置必须是由WriteComponentRes方法所写入的部件的位置。
ReadComponentRes 首先调用ReadResHeader方法从流中读取资源头,然后调用ReadComponent方法读取Instance。
如果流的当前位置不包含一个资源头。
ReadResHeader将引发一个EInvalidImage异常事件。
在Classes库单元中也包含一个名为ReadComponentRes的函数,该函数执行相同的操作,只不过它基于应用程序包含的资源建立自己的流。
8. ReadResHeader方法
声明:procedure ReadResHeader;
ReadResHeader方法从流的当前位置读取Windows资源文件头,并将流的当前位置指针移到该文件头的尾部。
如果流不包含一个有效的资源文件头,ReadResHeader将引发一个EInvalidImage异常事件。
流的ReadComponentRes方法在从资源文件中读取部件之前,会自动调用ReadResHeader方法,因此,通常程序员通常不需要自己调用它。
9. Seek方法
声明:function Seek(Offset: Longint; Origin: Word): Longint; virtual; abstract;
Seek方法将流的当前指针移动Offset个字节,字节移动的起点由Origin指定。
如果Offset是负数,Seek方法将从所描述的起点往流的头部移动。
下表中列出了Origin的不同取值和它们的含义:
表20.1 函数Seek的参数的取值
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━常量值Seek的起点Offset的取值
─────────────────────────────────SoFromBeginning 0 流的开头正数
SoFromCurrent 1 流的当前位置正数或负数
SoFromEnd 2 流的结尾负数
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
10. Write方法
在Delphi对象式管理的对象中有两类对象的方法都有称为Write的:Stream对象和Filer对象。
Stream对象的Write方法将数据写进流中。
Filer对象通过相关的流传递数据,在后文中会介绍这类方法。
Stream对象的Write方法声明如下:
function Write(const Buffer; Count: Longint): Longint; virtual; abstract;
Write方法将Buffer中的Count个字节写入流中,并将当前位置指针向流的尾部移动Count个字节,函数返回写入的字节数。
TStream的Write方法是抽象的,每个继承的Stream对象都要通过覆盖该方法来提供向特定存储媒介(内存、磁盘文件等)写数据的特定方法。
流的其它所有写数据的方法(如WriteBuffer、WriteComponent)都调用Write担当实际的写操作。
11. WriteBuffer方法
声明:procedure WriteBuffer(const Buffer; Count: Longint);
WriteBuffer的功能与Write相似。
WriteBuffer方法调用Write来执行实际的写操作,如果流没能写所有字节,WriteBuffer会触发一个EWriteError异常事件。
12. WriteComponent方法
在Stream对象和Filer对象都有被称为WriteComponent的方法。
Stream对象的WriteComponent方法将Instance所指定的部件和它所包含的所有部件都写入流中;Writer 对象的WriteComponent将指定部件的属性值写入Writer对象的流中。
Stream对象的WriteComponent方法声明是这样的:
procedure WriteComponent(Instance: Tcomponent);
WriteComponent创建一个Writer对象,并调用Writer的WriteRootComponent方法将Instance及其拥有的对象写入流。
13. WriteComponentRes方法
声明:WriteComponentRes(const ResName: String; Instance: TComponent);
WriteComponentRes方法首先往流中写入标准Windows 资源文件头,然后将Instance指定的部件写入流中。
要读由WriteComponentRes写入的部件,必须调用ReadComponentRes方法。
WriteComponentRes使用ResName传入的字符串作为资源文件头的资源名,然后调用WriteComponent方法将Instance和它拥有的部件写入流。
14. WriteDescendant方法
声明:procedure WriteDescendant(Instance Ancestor: TComponent);
Stream对象的WriteDescendant方法创建一个Writer对象,然后调入该对象的WriteDescendant方法将Instance部件写入流中。
Instance可以是从Ancestor部件继承的窗体,也可以是在从祖先窗体中继承的窗体中相应于祖先窗体中Ancestor部件的部件。
15. WriteDescendantRes方法
声明:procedure WriteDescendantRes(const ResName: String;
Instance, Ancestor: TComponent);
WriteDescendantRes方法将Windows资源文件头写入流,并使用ResName作用资源名,然后调用WriteDescendant方法,将Instance写入流。
20.1.1.2 TStream的实现原理
TStream对象是Stream对象的基础类,这是Stream对象的基础。
为了能在不同媒介上的存储数据对象,后继的Stream对象主要是在Read和Write方法上做了改进,。
因此,了解TStream是掌握Stream对象管理的核心。
Borland公司虽然提供了Stream对象的接口说明文档,但对于其实现和应用方法却没有提及,笔者是从Borland Delphi 2.0 Client/Server Suite 提供的源代码和部分例子程序中掌握了流式对象技术。
下面就从TStream的属性和方法的实现开始。
1. TStream属性的实现
前面介绍过,TStream具有Position和Size两个属性,作为抽象数据类型,它抽象了在各种存储媒介中读写数据所需要经常访问的域。
那么它们是怎样实现的呢?
在自定义部件编写这一章中介绍过部件属性定义中的读写控制。
Position和Size也作了读写控制。
定义如下:
property Position: Longint read GetPosition write SetPosition;
property Size: Longint read GetSize;
由上可知,Position是可读写属性,而Size是只读的。
Position属性的实现就体现在GetPosition和SetPosition。
当在程序运行过程中,任何读取Position的值和给Position赋值的操作都会自动触发私有方法GetPosition和SetPosition。
两个方法的声明如下:
function TStream.GetPosition: Longint;
begin
Result := Seek(0, 1);
end;
procedure TStream.SetPosition(Pos: Longint);
begin
Seek(Pos, 0);
end;
在设置位置时,Delphi编译机制会自动将Position传为Pos。
前面介绍过Seek的使用方法,第一参数是移动偏移量,第二个参数是移动的起点,返回值是移动后的指针位置。
Size属性的实现只有读控制,完全屏蔽了写操作。
读控制方法GetSize实现如下:
function TStream.GetSize: Longint;
var
Pos: Longint;
begin
Pos := Seek(0, 1);
Result := Seek(0, 2);
Seek(Pos, 0);
end;
2. TStream方法的实现
⑴CopyFrom方法
CopyFrom是Stream对象中很有用的方法,它用于在不同存储媒介中拷贝数据。
例如,内存与外部文件之间、内存与数据库字段之间等。
它简化了许多内存分配、文件打开和读写等的细节,将所有拷贝操作都统一到Stream对象上。
前面曾介绍:CopyFrom方法带Source和Count两个参数并返回长整型。
该方法将Count个字节的内容从Source拷贝到当前流中,如果Count值为0则拷贝所有数据。
function TStream.CopyFrom(Source: TStream; Count: Longint): Longint;
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;
⑵ReadBuffer方法和WriteBuffer方法
ReadBuffer方法和WriteBuffer方法简单地调用虚拟函数Read、Write来读写流中数据,它比Read和Write增加了读写数据出错时的异常处理。
procedure TStream.ReadBuffer(var Buffer; Count: Longint);
begin
if (Count <> 0) and (Read(Buffer, Count) <> Count) then
raise EReadError.CreateRes(SReadError);
end;
procedure TStream.WriteBuffer(const Buffer; Count: Longint);
begin
if (Count <> 0) and (Write(Buffer, Count) <> Count) then
raise EWriteError.CreateRes(SWriteError);
end;
⑶ReadComponent、ReadResHeader和ReadComponentRes方法
ReadComponent方法从当前流中读取部件。
在实现上ReadComponent方法创建了一个TStream对象,并用TReader的ReadRootComponent方法读部件。
在Delphi对象式管理中,Stream对象和Filer对象结合很紧密。
Stream对象的许多方法的实现需要Filer 对象的支持,而Filer对象的构造函数直接就以Stream对象为参数。
在ReadComponent 方法的实现中就可清楚地看到这一点:
function TStream.ReadComponent(Instance: TComponent): TComponent;
var
Reader: TReader;
begin
Reader := TReader.Create(Self, 4096);
try
Result := Reader.ReadRootComponent(Instance);
finally
Reader.Free;
end;
end;
ReadResHeader方法用于读取Windows资源文件的文件头,由ReadComponentRes 方法在读取Windows资源文件中的部件时调用,通常程序员不需自己调用。
如果读取的不是资源文件ReadResHeader,将触发异常事件。
procedure TStream.ReadResHeader;
var
ReadCount: Longint;
Header: array[0..79] of Char;
begin
FillChar(Header, SizeOf(Header), 0);
ReadCount := Read(Header, SizeOf(Header) - 1);
if (Byte((@Header[0])^) = $FF) and (Word((@Header[1])^) = 10) then
Seek(StrLen(Header + 3) + 10 - ReadCount, 1)
else
raise EInvalidImage.CreateRes(SInvalidImage);
end;
ReadComponentRes在Windows资源文件中读取部件,为了判断是否是资源文件,它首先调用ReadResHeader方法,然后调用ReadComponent方法读取Instance指定的部件。
下面是它的实现:
function TStream.ReadComponentRes(Instance: TComponent): TComponent;
begin
ReadResHeader;
Result := ReadComponent(Instance);
end;
与ReadComponentRes相应的写方法是WriteComponentRes,Delphi 调用这两个方法读写窗体文件(DFM文件),在后面书中会举用这两个方法读取DFM文件的例子。
⑷WriteComponent和WriteDescendant方法
Stream对象的WriteDescendant方法在实现过程中,创建了TWriter对象,然后利用TWriter的WriteDescendant方法将Instance写入流。
而WriteComponent方法只是简单地调用WriteDescendant方法将Instance写入流。
它们的实现如下:
procedure TStream.WriteComponent(Instance: TComponent);
begin
WriteDescendent(Instance, nil);
end;
procedure TStream.WriteDescendent(Instance, Ancestor: TComponent);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Self, 4096);
try
Writer.WriteDescendent(Instance, Ancestor);
finally
Writer.Free;
end;
end;
⑸WriteDescendantRes和WriteComponentRes方法
WriteDescendantRes方法用于将部件写入Windows资源文件;而WriteComponentRes 方法只是简单地调用WriteDescendantRes方法,它们的实现如下:
procedure TStream.WriteComponentRes(const ResName: string; Instance:
TComponent);
begin
WriteDescendentRes(ResName, Instance, nil);
end;
procedure TStream.WriteDescendentRes(const ResName: string; Instance,
Ancestor: TComponent);
var
HeaderSize: Integer;
Origin, ImageSize: Longint;
Header: array[0..79] of Char;
begin
Byte((@Header[0])^) := $FF;
Word((@Header[1])^) := 10;
HeaderSize := StrLen(StrUpper(StrPLCopy(@Header[3], ResName, 63))) + 10;
Word((@Header[HeaderSize - 6])^) := $1030;
Longint((@Header[HeaderSize - 4])^) := 0;
WriteBuffer(Header, HeaderSize);
Origin := Position;
WriteDescendent(Instance, Ancestor);
ImageSize := Position - Origin;
Position := Origin - 4;
WriteBuffer(ImageSize, SizeOf(Longint));
Position := Origin + ImageSize;
WriteCompnentRes是与ReadComponentRes相应的对象写方法,这两个方法相互配合可读取Delphi的DFM文件,从而利用Delphi系统的功能。
20.1.2 THandleStream对象
THandleStream对象的行为特别象FileStream对象,所不同的是它通过已创建的文件句柄而不是文件名来存储流中的数据。
THandleStream对象定义了Handle属性,该属性提供了对文件句柄的只读访问,并且Handle属性可以作为Delphi的RTL文件管理函数的参数,利用文件类函数来读写数据。
THandleStream覆盖了构造函数Create,该函数带有Handle 参数,该参数指定与THandleStream对象相关的文件句柄。
20.1.2.1 THandleStream的属性的方法:
1. Handle属性
声明:property Handle: Integer;
Handle属性提供了对文件句柄的只读访问,该句柄由THandleStream的构造方法Create传入。
因此除了用THandleStream提供的方法外,也可以用文件管理函数对句柄进行操作。
实际上,THandleStream的方法在实现上也是运用文件管理函数进行实际的读写操作。
2. Create方法
声明:constructor Create(AHandle: Integer);
Create方法使用传入的Handle参数创建一个与特定文件句柄相联的THandleStream 对象,并且将AHandle赋给流的Handle属性。
3. Read、Write和Seek方法
这三个方法是TStream的虚方法,只是在THandleStream 中覆盖了这三个方法,以实现特定媒介──文件的数据存取。
后面会详细介绍这三个方法的实现。
20.1.2.2 THandleStream的实现原理
THandleStream是从TStream继承来的,因此可以共用TStream中的属性和大多数方法。
THandleStream在实现上主要是增加了一个属性Handle和覆盖了Create、Read、Write和Seek四个方法。
1. 属性的实现
Handle属性的实现正如Delphi大多数属性的实现那样,先在对象定义的private部分声明一个存放数据的变量FHandle,然后在定义的public部分声明属性Handle,其中属性定义的读写控制部分加上只读控制,读控制只是直接读取FHandle变量的值,其实现如下:
THandleStream = class(TStream)
private
FHandle: Integer;
…
property Handle: Integer read FHandle;
end;
2. 方法的实现
THandleStream的Create方法,以AHandle作为参数,在方法里面只是简单的将AHandle的值赋给FHandle,其实现如下:
constructor THandleStream.Create(AHandle: Integer);
begin
FHandle := AHandle;
end;
为实现针对文件的数据对象存储,THandleStream的Read、Write和Seek方法覆盖了TStream中的相应方法。
它们的实现都调用了Windows的文件管理函数。
Read方法调用FileRead函数实现文件读操作,其实现如下:
function THandleStream.Read(var Buffer; Count: Longint): Longint;
begin
Result := FileRead(FHandle, Buffer, Count);
if Result = -1 then Result := 0;
end;
Write方法调用FileWrite函数实现文件写操作,其实现如下:
function THandleStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := FileWrite(FHandle, Buffer, Count);
if Result = -1 then Result := 0;
end;
Seek方法调用FileSeek函数实现文件指针的移动,其实现如下:
function THandleStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
Result := FileSeek(FHandle, Offset, Origin);
end;
20.1.3 TFileStream对象
TFileStream对象是在磁盘文件上存储数据的Stream对象。
TFileStream是从THandleStream继承下来的,它和THandleStream一样都是实现文件的存取操作。
不同之处在于THandleStream用句柄访问文件,而TFileStream用文件名访问文件。
实际上
TFileStream是THandleStream上的一层包装,其内核是THandleStream的属性和方法。
TFileStream中没有增加新的属性和方法。
它只是覆盖了的构造方法Create和析构方法Destory。
在Create方法中带两个参数FileName和Mode。
FileName描述要创建或打开的文件名,而Mode描述文件模式如fmCreate、fmOpenRead和fmOpenWrite等。
Create 方法首先使用FileCreate或FileOpen函数创建或打开名为FileName的文件,再将得到的文件句柄赋给FHandle。
TFileStream的文件读写操作都是由从THandleStream继承的Read、Write和Seek方法来完成。
下面是Create方法的实现。
constructor TFileStream.Create(const FileName: string; Mode: Word);
begin
if Mode = fmCreate then
begin
FHandle := FileCreate(FileName);
if FHandle < 0 then
raise EFCreateError.CreateResFmt(SFCreateError, [FileName]);
end else
begin
FHandle := FileOpen(FileName, Mode);
if FHandle < 0 then
raise EFOpenError.CreateResFmt(SFOpenError, [FileName]);
end;
end;
如果Create方法中的文件创建或打开出错将触发一个异常事件。
TFileStream的Destroy方法在FHande不小于零时,关闭文件。
下面是Destroy的实现。
destructor TFileStream.Destroy;
begin
if FHandle >= 0 then FileClose(FHandle);
end;
20.1.4 TCustormMemoryStream对象
TCustomMemoryStream是用于对内存媒介上的数据进行操作的对象,但它是抽象类,是用于内存流如TMemoryStream和TResourceStream的通用祖先对象。
它定义了许多对于后继MemoryStream对象很重要的属性和方法。
因此,虽然不直接使用TCustomMemoryStream对象,但也要详细介绍该对象,掌握它是掌握MemoryStream对象的基础。
TCustomMemoryStream从抽象类TStream继承而来。
它定义了Memory属性,该属性用来访问流的内部数据存储单元。
它还覆盖了继承的Read和Write方法,实现了对内存的读写访问操作,并且还提供了将内存中数据存入磁盘文件和复制到其它流的方法。
下面介绍它的属性和方法。
20.1.4.1 TCustomMemoryStream的属性和方法
1. Memory属性
声明:property Memory: Pointer;
通过Memory属性可以对为内存流分配的内存缓冲池直接访问。
Memory是个只读型的属性。
如果要改变内存分配,可以调用SetPointer方法分配内存,并将指针分配给Memory 属性。
2. SetPointer方法
声明:procedure SetPointer(Ptr: Pointer; Size: Longint);
SetPointer方法将由Ptr传入的指针值赋给用于Memory属性的内部数据域,并将流的Size属性值设为由Size传入的值。
在MemoryStream对象中调用SetPointer方法可以为流的存储分配或重分配内存。
3. SaveToStream方法
声明:procedure SaveToStream(Stream: TStream);
SaveToStream方法将内存流中的完整数据写入Stream所指定的流。
TCustomMemoryStream中没有定义相应的LoadFromStream方法,但在可实例化的TMemoryStream中定义了该方法。
4. SaveToFile方法
声明:procedure SaveToFile(const FileName: string);
SaveToFile方法将内存流中的完整内容写入由FileName指定的文件。
如果指定文件已经存在,则SaveToFile会覆盖它。
这个方法使文件成为内存流内容的一份完整的拷贝。
TCustomMemoryStream方法还覆盖了TStream中的Read和Seek方法。
有关它们的介绍可以参考TStream对象的介绍,在实现原理部分还将介绍这两个方法。
20.1.4.2 TCustomMemoryStream方法的实现原理
TCustomMemoryStream方法继承了TStream对象,因此拥有了TStream的属性和大多数方法。
它还增加了Memory属性,SetPointer、SaveToStream和SaveToFile方法,并且覆盖了Read和Seek方法,以实现它在内存流上的特殊操作,下面介绍它们的实现。
1. Memory属性和SetPointer方法
如同Delphi自定义部件开发一章介绍的那样,Memory属性在实现上也在private部分定义了存放属性值的数据域FMemory。
并且在属性声明中加上只读控制。
Memory属性的读控制只是简单地读取FMemory的值。
它的实现如下:
TCustomMemoryStream = class(TStream)
private
FMemory: Pointer;
FSize, FPosition: Longint;
…
public
…
property Memory: Pointer read FMemory;
end;
TStream的Size和Position属性的实现并没有在private部分声明一个数据域保存属性而是加上读写控制方法来提供属性值。
在TCustomMemoryStream中定义了FSize和FPosition域,它们用于对象的内部使用以完成类似Size和Position的功能。
在TCustomMemoryStream的许多方法中都用来了这两个域,因此先介绍一下。
TCustomMemoryStream的SetPointer方法用到了FSize域。
SePointer方法比较简单,它将传入的Ptr和Size值赋给FMemory和FSize,但并不执行内存的分配工作,实现如下:
procedure TCustomMemoryStream.SetPointer(Ptr: Pointer; Size: Longint);
begin
FMemory := Ptr;
FSize := Size;
end;
TCustomMemoryStream中的许多方法都是这种类型,貌似平淡无奇,但要记住TCustomMemoryStream是个抽象类,它的主要功能是搭框架,许多方法的具体实现体现在一些可实例化的对象如TMemoryStream中,这正是面向对象技术的精华所在,也是TCustomMemoryStream设计的妙处所在。
2. Read和Seek方法
前面讲了TCustomMemoryStream是个抽象类,不可实例化,它的许多域都没有初始化,如FMemory自始至终都只是没有指向实际内存区域的指针,内存池的分配都在后继的可实例化的对象中执行,如TMemory中,后面会介绍内存池在不同内存流中是如何分配的。
TCustomMemoryStream的Read和Seek方法都是只对FMemroy、FSize和FPosition 进行操作。
Read方法调用了内存拷贝函数Move实现读操作,并且相应地移动指针位置,即设置FPosition的值。
实现如下:
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;
如果Count为0或从当前指针到流的尾部只有零字节,则返回值为零。
如果Count值大于当前指针到流的尾部的字节数即剩余字节数,则只读剩余字节数。
因此在具体运用内存流的Read方法时要判断是否按需求读取数据了。
Seek方法也是通过给Position和FSize数据域赋值来实现指针移动,其实现如下:
function TCustomMemoryStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
case Origin of
0: FPosition := Offset;
1: Inc(FPosition, Offset);
2: FPosition := FSize + Offset;
end;
Result := FPosition;
end;
Offse代表移动的偏移量。
Origin代表移动的起点,值为0表示从文件头开始,值为1表示从当前位置开始,值为2表示从文件尾往前,这时OffSet一般为负数。
Seek的实现没有越界的判断。
3. SaveToStream和SaveToFile方法
SaveToStream方法是将MemoryStream对象中的内容写入Stream所指定的流。
其实现如下:
procedure TCustomMemoryStream.SaveToStream(Stream: TStream);
begin
if FSize <> 0 then Stream.WriteBuffer(FMemory^, FSize);
end;
SaveToStream方法调用了Stream的WriteBuffer方法,直接将FMemory中的内容按FSize字节长度写入流中。
SaveToFile方法是与SaveToStream方法相关的。
SaveToFile方法首先创建了一个FileStream对象,然后把该文件Stream对象作为SaveToStream的参数,由SaveToStream 方法执行写操作,其实现如下:
procedure TCustomMemoryStream.SaveToFile(const FileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
在Delphi 的许多对象的SaveToStream 和SaveToFile、LoadFromStream和LoadFromFile方法的实现都有类似的嵌套结构。
20.1.5 TMemoryStream对象
TMemoryStream对象是一个管理动态内存中的数据的Stream对象,它是从TCustomMemoryStream中继承下来的,除了从TCustomMemoryStream中继承的属性和方法外,它还增加和覆盖了一些用于从磁盘文件和其它注台读数据的方法。
它还提供了写入、消除内存内容的动态内存管理方法。
下面介绍它的这些属性和方法。
20.1.5.1 TMemoryStream的属性和方法
1. Capacity属性
声明:property Copacity: Longint;
Capacity属性决定了分配给内存流的内存池的大小。
这与Size属性有些不同。
Size 属性是描述流中数据的大小。
在程序中可以将Capacity 的值设置的比数据所需最大内存大一些,这样可以避免频繁地重新分配。
2. Realloc方法
声明:function Realloc(var NewCapacity: Longint): Pointer; virtual;
Realloc方法,以8K为单位分配动态内存,内存的大小由NewCapacity指定,函数返回指向所分配内存的指针。
3. SetSize方法
SetSize方法消除内存流中包含的数据,并将内存流中内存池的大小设为Size字节。
如果Size为零,是SetSize方法将释放已有的内存池,并将Memory属性置为nil;否则,SetSize方法将内存池大小调整为Size。
4. Clear方法
声明:procedure Clear;
Clear方法释放内存中的内存池,并将Memory属性置为nil。
在调用Clear方法后,Size和Position属性都为0。
5. LoadFromStream方法
声明:procedure LoadFromStream(Stream: TStream);
LoadFromStream方法将Stream指定的流中的全部内容复制到MemoryStream中,复制过程将取代已有内容,使MemoryStream成为Stream的一份拷贝。
6. LoadFromFile方法
声明:procedure LoadFromFile(count FileName: String);
LoadFromFile方法将FileName指定文件的所有内容复制到MemoryStream中,并取代已有内容。
调用LoadFromFile方法后,MemoryStream将成为文件内容在内存中的完整拷贝。
20.1.5.2 TMemoryStream对象的实现原理
TMemoryStream从TCustomMemoryStream对象直接继承,因此可以享用TCustomMemoryStream的属性和方法。
前面讲过,TCustomMemoryStream是用于内存
中数据操作的抽象对象,它为MemoryStream对象的实现提供了框架,框架中的内容还要由具体MemoryStream对象去填充。
TMemoryStream对象就是按动态内存管理的需要填充框架中的具体内容。
下面介绍TMemoryStream对象的实现。
1. TMemoryStream属性的实现
TMemoryStream在其protected部分增加了一个Capacity属性,该属性决定了MemoryStream所占动态内存的大小。
TMemoryStream首先在private部分声明了FCapacity变量作为存储Capacity属性值的数据域,然后在protected部分声明了该属性。
在属性声明的读控制部分简单读取FCapacity的值,在写控制处调用了方法SetCapacity。
该方法除了给FCapacity赋值外还执行了修改Capacity属性所必需操作如状态改变等。
下面是属性的实现:
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
…
property Capacity: Longint read FCapacity write SetCapacity;
public
…
end;
写控制方法SetCapacity的实现是这样的:
procedure TMemoryStream.SetCapacity(NewCapacity: Longint);
begin
SetPointer(Realloc(NewCapacity), FSize);
FCapacity := NewCapacity;
end;
在SetCapacity 方法先是调用Realloc重新分配内存,然后用NewCapacity的值给FCapacity赋值。
Realloc方法进行某些对象状态的改变。
2. TMemoryStream对象方法的实现
⑴Realloc方法
Realloc方法是TMemoryStream动态内存分配的核心,它的SetSize、SetCapacity 等方法最终都是调用Realloc进行内存的分配和初始化工作的。
它的实现如下:
const
MemoryDelta = $2000;
function TMemoryStream.Realloc(var NewCapacity: Longint): Pointer;
begin
if NewCapacity > 0 then
NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);
Result := Memory;
if NewCapacity <> FCapacity then
begin
if NewCapacity = 0 then
begin
GlobalFreePtr(Memory);
Result := nil;
end else
begin
if Capacity = 0 then
Result := GlobalAllocPtr(HeapAllocFlags, NewCapacity)
else
Result := GlobalReallocPtr(Memory, NewCapacity, HeapAllocFlags);
if Result = nil then raise EStreamError.CreateRes(SMemoryStreamError);
end;
end;
end;
Realloc方法是以8K为单位分配动态内存的,方法中的第一句if语句就是执行该操作。
如果传入的NewCapacity参数值为0,则释放流中的内存。
Realloc方法用GLobal FreePtr 函数释放内存,用GlobalAllocPtr分配内存,用GlobalReallocPtr进行内存的重分配。
如果原来的Capacity属性值为0,则调用Globa|AllocPtr否则调用GlobalReallocPtr。
最后如果Result为nil则触发内存流错的异常事件,否则返回指向分配的内存的指针。
⑵Write方法
Write方法从内存流内部缓冲池的当前位置开始写入二进制数据。
其实现如下:
function TMemoryStream.Write(const Buffer; Count: Longint): Longint;
var
Pos: Longint;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
Pos := FPosition + Count;
if Pos > 0 then
begin
if Pos > FSize then
begin
if Pos > FCapacity then
SetCapacity(Pos);
FSize := Pos;
end;
System.Move(Buffer, Pointer(Longint(FMemory) + FPosition)^, Count);
FPosition := Pos;
Result := Count;
Exit;
end;
end;
Result := 0;
end;
Buffer中存储要写入流的二进制数据,如果要写入的数据的字节超出了流的内存池的大小,则调用SetCapacity方法再分配内存,然后用内存复制函数将Buffer中的数据复制到FMemory中。
接着移动位置指针,并返回写入数据的字节数。
分析这段程序可以知道,FCapacity的值和FSize的值是不同的。
⑶Clear方法
Clear方法消除内存流中的数据,将Memory属性置为nil,并将FSize和FPosition 的值设为0。
其实现如下:
procedure TMemoryStream.Clear;
begin
SetCapacity(0);
FSize := 0;
FPosition := 0;
end;
⑷LoadFromStream和LoadFromFile方法
LoadFromStream方法首先根据传入的Stream的Size属性值重新分配动态内存,然后调用Stream的ReadBuffer方法往FMemory中复制数据,结果Stream的全部内容在内存中有了一份完整拷贝。
其实现如下:
procedure TMemoryStream.LoadFromStream(Stream: TStream);
var
Count: Longint;
begin
Stream.Position := 0;
Count := Stream.Size;
SetSize(Count);
if Count <> 0 then Stream.ReadBuffer(FMemory^, Count);
end;
LoadFromFile与LoadFromStream是一对方法。
LoadFromFile首先创建了一个TFileStream对象,然后调用LoadFromStream方法,将FileStream文件流中的数据写入MemoryStream中。
20.1.6 TResourceStream对象
TResourceStream对象是另一类MemoryStream对象,它提供对Windows 应用程序资源的访问,因此称它为资源流。
TResourceSream也是从TCustomMemoryStream 继承。