14. 4 节介绍了格式化和写入顺序访问文件的数据修改时会有破坏文件中其他数据的危险。例如,如果要把名字"White"改为"Worthinglon",则不是简单地重定义旧的名字。White的记录是以如下形式写人文件中的:
300 White 0.00
如果用新的名字从文件中相同的起始位置重写该记录,记录的格式就成为:
300 Worthington 0.00
因为新的记录长度大于原始记录的长度,所以从“Worthington"的第二个“0”之后的字符将重定义文件中的下一条顺序记录。出现该问题的原因在于:在使用流插入运算符<<和流读取运算符>>的格式化输人,输出模型中,域的大小是不定的,因而记录的大小也是不定的。例如,7、14、-117、2047和27383都是int类型的值,虽然它们的内部存储占用相同的字节数,但是将它们以格式化文本打印到屏幕上或存储在磁盘上时要占用不同大小的域。因此,格式化输入,输出模型通常不用来更新已有的记录。
也可以修改上述名字,但比较危险。比如,在300 Whlte 0.00之前的记录要复制到一个新的文件中,然后写入新的记录并把300 White 0.00之后的记录复制到新文件中。这种方法要求在更新一条记录时处理文件中的每一条记录。如果文件中一次要更新许多记录,则可以用这种方法。
前面介绍了生成顺序访问文件和从顺序访问文件搜索特定信息。顺序访问文件不适合快速访问应用程序,即要立即找到特定记录的信息。快速访问应用程序的例子有航空订票系统、银行系统、销售网点系统、自动柜员机和其他要求快速处理特定数据的事务处理系统(transaction processing system)。银行要面对成千上万的客户,但自动柜员机能在瞬间作出响应。这种快速访问应用程序是用随机访问文件(random access file)实现的。随机访问文件的各个记录可以直接快速访问,而不需要进行搜索。
前面曾介绍过.C++不提供文件结构。因此应用程序要自己生成随机访问文件。虽然实现随机访问文件还有其他方法,但是本书的讨论只限于使用定长记录的这种简洁明了的方法。因为随机访问文件中的每一条记录都有相同的长度,所以能够用记录关键字的函数计算出每一条记录相对于文件起始点的位置。不久就会学到怎样立即访问到文件甚至大型文件中指定的记录。
图14.10 反映了由定长记录组成的随机访问文件的一种观点(每一条记录为100字节)。它就像一列火车,一些车箱是空的,还有一些车箱满载货物,但是火车中的每节车箱具有相同的长度。
可以在不破坏其他数据的情况下把数据插入到随机访问文件中。也能在不重写整个文件的情况下更新和删除以前存储的数据。下面几节要讨论怎样建立随机访问文件、键入数据、顺序和随机地读取数据、更新数据和删除不再需要的数据。
ostream成员函数write把从内存中指定位置开始的固定个数的字节送到指定流中,当流与文件关联时,数据写人到“put'’文件位置指针所指示的位置。istream成员函数read把固定个数的字节从指定流输入到内存中指定地址开始的区域。如果流与文件相关联,则该字节从"get"文件位置指针指定的文件地址开始输入。
现在,将整型number写入文件时,不是用下列语句:
outFile << number;
对4字节整数打印1位或ll位(10位加一个符号位,各要1字节存储空问),而改用:
outFile.write( reinterpret_cast( &number ), sizeof( number ));
这种方法总是写入4字节(在4字节整数机器上)。write函数要求一个const char *类型的参数为第一个参数,因此我们用reinterpret_cast<const char*>强制类型转换运算符将number的地址变为const char *指针。write的第二个参数是size_t类型的整数,指定写入的字节数。可以看出,istresm函数read可以将4个字节读回到整型变量number中。
随机存取文件处理程序很少只把一个域写入文件中,通常会一次写入一个结构或一个类对象。
下面举一个例子。
考虑如下的问题描述:
建立一个能够存储100个定长记录的借贷处理系统。每一条记录由账号(用作记录关键字)、姓、名和借贷金额组成。程序要能够更新、插入和删除一条记录以及能够以格式化文本形式列出所有的记录。要求使用随机访问文件。
以下几节介绍了建立借贷处理程序所需的技术。图14.11中的程序说明了怎样打开一个随机访问文件、怎样用struct定义—条记录格式(在cIntdata.h头文件中定义)以及怎样把数据写入磁盘。
程序用write函数和空结构初始化了文件"credit.dat"的所有100条记录。每一个空结构中,账号都为0,姓氏和名为NULL,借贷金额为0.0。文件以这种方式初始化后就在磁盘上建立了存储文件的空间,并且能够确定某条记录是否包含数据。
在图14.11中,下列语句(第34行到第36行):
outCredit.write( reinterpret cast(&blankClient),
将长度为sizeof(clientData)的blankClient结构写入与ofstream的对象outCredit相关联的文件credit.dat。记住,运算符sizeof返回括号中对象的长度(字节数,见第5章)。注意,第34行函数write的第一个参数应为const char*类型,但 &blankClient的数据类型为clientData *。要将&blankClient变为相应指针类型,下列表达式:
reinterpret cast<const char *>( &blankClient )
用强制类型转换运算符reinterpret_cast将blankClient地址变为const char *类型,因此调用write能顺利编译,而不产生语法错误。
// Fig. 14.11: clntdata.h // Definition of struct clientData used in // Figs. 14.11, 14.12, 14.14 and 14.15. #ifndef CLNTDATA_H #define CLNTDATA_H struct clientData { int accountNumber; char lastName[15]; char firstName[10]; float balance; } ; #endif // Fig. 14.11: fig14_11.cpp // Creating a randomly accessed file sequentially #include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include "clntdata.h" int main() { ofstream outCredit( "credit.dat", ios::out ); if ( !outCredit ) { cerr << "File could not be opened." << endl; exit( 1 ); } clientData blankClient = { 0, "", "", 0.0 }; for ( int i = 0; i < 100; i++ ) outCredit.write( reinterpret_cast( &blankClient ), sizeof( clientData ) ); return 0; }
图14. 11 顺序生成随机访问文件