tstream对象
tstream对象是能在各种媒介中存储二进制数据的对象的抽象对象。从tstream 对象继承的对象用于在内存、windows资源文件、磁盘文件和数据库字段等媒介中存储数据。
stream中定义了两个属性:size和position。它们分别以字节为单位表示的流的大小和当前指针位置。tstream中定义的方法用于在各种流中读、写和相互拷贝二进制数据。因为所有的stream对象都是从tstream中继承来的,所以在tstream中定义的域和方法都能被stream对象调用和访
问。此外,又由于面向对象技术的动态联编功能,tstream为各种流的应用提供了统一的接口,简化了流的使用;不同stream对象是抽象了对不同存储媒介的数据上的操作,因此,tstream的需方法为在不同媒介间的数据拷贝提供了最简捷的手段。
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的不同取值和它们的含义:
函数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写入流。
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资源文件中的部件时调用,通常程序员不需自己调用。如果读取的不是资源文件readresh := 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方法的实现都有类似的嵌套结构。
tmemorystream对象
tmemorystream对象是一个管理动态内存中的数据的stream对象,它是从tcustommemorystream中继承下来的,除了从tcustommemorystream中继承的属性和方法外,它还增加和覆盖了一些用于从磁盘文件和其它注台读数据的方法。它还提供了写入、消除内存内容的动态内存管理方法。下面
介绍它的这些属性和方法。
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将成为文件内容在内存中的完整拷贝。
tmemorystream对象的实现原理
tmemorystream从tcustommemorystream对象直接继承,因此可以享用tcustommemorystream的属性和方法。前面讲过,tcustommemorystream是用于内存中数据操作的抽象对象,它为memorystream对象的实现提供了框架,框架中的内容还要由具体memorystream对象去填充。tmemorystrea
m对象就是按动态内存管理的需要填充框架中的具体内容。下面介绍tmemorystream对象的实? fbuffer := allocmem(fdataset.recordsize);
frecord := fbuffer;
if not fdataset.getcurrentrecord(fbuffer) then exit;
openmode := dbireadonly;
end else
begin
if not (fdataset.state in [dsedit, dsinsert]) then dberror(snotediting);
openmode := dbireadwrite;
end;
check(dbiopenblob(fdataset.handle, frecord, ffieldno, openmode));
end;
fopened := true;
if mode = bmwrite then truncate;
end;
该方法首先是用传入的field参数给ffield,fdataset,frecord和ffieldno赋值。方法中用allocmem按当前记录大小分配内存,并将指针赋给fbuffer,用dataset部件的getcurrentrecord方法,将记录的值赋给fbuffer,但不包括blob数据。
方法中用到的dbiopenblob函数是bde的api函数,该函数用于打开数据库中的blob字段。
最后如果方法传入的mode参数值为bmwrite,就调用truncate将当前位置指针以后的
数据删除。
分析这段源程序不难知道:
● 读写blob字段,不允许blob字段所在dataset部件有filter,否则产生异常事件
● 要读写blob字段,必须将dataset设为编辑或插入状态
● 如果blob字段中的数据作了修改,则在创建blob 流时,不再重新调用dbiopenblob函数,而只是简单地将fopened置为true,这样可以用多个blob 流对同一个blob字段读写
destroy方法释放blob字段和为fbuffer分配的缓冲区,其实现如下:
destructor tblobstream.destroy;
begin
if fopened then
begin
if fmodified then ffield.fmodified := true;
if not ffield.fmodified then
dbifreeblob(fdataset.handle, frecord, ffieldno);
end;
if fbuffer <> nil then freemem(fbuffer, fdataset.recordsize);
if fmodified then
try
ffield.datachanged;
except
application.handleexception(self);
end;
end;
如果blob流中的数据作了修改,就将ffield的fmodified置为true;如果ffield的modified为false就释放blob字段,如果fbuffer不为空,则释放临时内存。最后根据fmodified的值来决定是否启动ffield的事件处理过程datachanged。
不难看出,如果blob字段作了修改就不释放blob字段,并且对blob 字段的修改只有到destroy时才提交,这是因为读写blob字段时都避开了ffield,而直接调用bde api函数。这一点是在应用bde api编程中很重要,即一定要修改相应数据库部件的状态。
2. read和write方法的实现
read和write方法都调用bde api函数完成数据库blob字段的读写,其实现如下:
function tblobstream.read(var buffer; count: longint): longint;
var
status: dbiresult;
begin
result := 0;
if fopened then
begin
status := dbigetblob(fdataset.handle, frecord, ffieldno, fposition,
count, @buffer, result);
case status of
dbierr_none, dbierr_endofblob:
begin
if ffield.ftransliterate then
nativetoansibuf(fdataset.locale, @buffer, @buffer, result);
inc(fposition, result);
end;
dbierr_invalidbloboffset:
{nothing};
else
dbierror(status);
end;
end;
end;
read方法使用了bde
api的dbigetblob函数从fdataset中读取数据,在本函数中,各参数的含义是这样的:fdataset.handle代表dataset的bde句柄,freacord表示blob字段所在记录,ffieldno表示blob字段号,fposition表示要读的的数据的起始位置,count表示要读的字节数,buffer是读出数据所占的内存,
result是实际读出的字节数。该bde函数返回函数调用的错误状态信息。
read方法还调用了nativetoansibuf进行字符集的转换。
function tblobstream.write(const buffer; count: longint): longint;
var
temp: pointer;
begin
result := 0;
if fopened then
begin
if ffield.ftransliterate then
begin
getmem(temp, count);
try
ansitonativebuf(fdataset.locale, @buffer, temp, count);
check(dbiputblob(fdataset.handle, frecord, ffieldno, fposition,
count, temp));
finally
freemem(temp, count);
end;
end else
check(dbiputblob(fdataset.handle, frecord, ffieldno, fposition,
count, @buffer));
inc(fposition, count);
result := count;
fmodified := true;
end;
end;
write方法调用了bde api的dbiputblob函数实现往数据库blob字段存储数据。
该函数的各参数含义如下:
调用函数dbiputblob的各传入参数的含义
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
参数名 含义
──────────────────────────────
fdatasethandle 写入的数据库的bde句柄
frecord 写入数据的blob字段所在的记录
ffieldno blob字段号
fposition 写入的起始位置
count 写入的数据的字节数
buffer 所写入的数据占有的内存地址
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
标志,该标志意味着后面存储有一连串的项目。reader对象,在读这一连串项目时先调用readlistbegin方法读取该标志位,然后用endoflist判断是否列表结束,并用循环语句读取项目。在调用writelistbegin方法的后面必须调用writelistend方法写列表结束标志,相应的在reader对象中
有readlistend方法读取该结束标志。
5. writelistend方法
声明:procedure writelistend;
writelistend方法在流中,写入项目列表结束标志,它是与writelistbegin相匹配的方法。
6. writeboolean方法
声明:procedure writeboolean(value: boolean);
writeboolean方法将value传入的布尔值写入流中。
7. writechar方法
声明:procedure writechar(value: char);
writechar方法将value中的字符写入流中。
8. writefloat方法
声明:procedure writefloat(value: extended);
writefloat方法将value传入的浮点数写入流中。
9. writeinteger方法
声明:procedure writeinteger(value: longint);
writeinteger方法将value中的整数写入流中。
10. writestring方法
声明:procedure writestring(const value: string);
writestring方法将value中的字符串写入流中。
11. writeident方法
声明:procedure writeident(const ident: string);
writeident方法将ident传入的标识符写入流中。
12. writesignature方法
声明:procedure writesignature;
writesignature方法将delphi filer对象标签写入流中。writerootcomponent方法在将部件写入流之前先调用writesignature方法写入filer标签。reader对象在读部件之前调用readsignature方法读取该标签以指导读操作。
13. writcomponent方法
声明:procedure writecomponent(component: tcomponent);
writecomponent方法调用参数component的writestate方法将部件写入流中。在调用writestate之前,writecomponent还将component的componetnstate属性置为cswriting。当writestate返回时再清除cswriting.
14. writerootcomponent方法
声明:procedure writerootcomponent(root: tcomponent);
writerootcomponent方法将writer对象root属性设为参数root带的值,然后调用writesignature方法往流中写入filer对象标签,最后调用writecomponent方法在流中存储root部件。