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; CouNG="ZH-CN"> 资源文件中的部件时调用,通常程序员不需自己调用。如果读取的不是资源文件 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; end;
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; public … 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 >< P> 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 中。 |