当我们使用文件"读"的功能时,DOS会从磁盘上一次读入512个字节的数据到它的缓冲区中(假设只有一个缓冲区),然后根据一个记录的长度将其中相应数量的数据传到DTA中。比如当前一个记录的长度是128字节,执行一次读功能时DOS会从磁盘读入当前记录处的连续512个字节,然后从缓冲区中拨出128字节传到DTA中供程序处理。
如果我们要读入下一个记录,这时DOS就会直接从缓冲区中取出下面128个字节送至DTA,而不是再从磁盘读取。可见在这种情况下我们读取4个记录时磁盘只需转动一次。由于磁盘机是个机械电子结合的设备,工作速度很慢,所以减少磁盘机启动次数就可以提高系统的效率,这就是DOS的文件缓冲区所起的作用。
写盘的时候也是这样,当程序写出一个记录时DOS会将这个记录保存在缓冲区中,而不是直接送到磁盘。只有数据攒到一定数量后DOS才会将这些数据一次写入磁盘。这样做也是为了减少磁盘机启动次数,提高效率。
但是这样做就有一个不好的结果,当我们的程序将数据写出后如果未做其它操作就直接结束了,那么写出的数据究竟是在缓冲区中还是在磁盘中呢?结果是不确定的。如果数据仍然在DOS缓冲区中而未存入磁盘,那么程序结束后这些数据便遗失在内存之中。所以在数据写出后程序结束前我们必须做些额外的工作,这就是"关闭文件"。DOS执行"关闭文件"功能的实质工作之一就是将缓冲区中未凑够数的数据写入磁盘以免丢失。
有一点需要注意,文件的内容固然重要,有关这个文件的特征信息同样是重要的。比如说文件的长度,日期和时间等。如果我们给一个文件追加了数据,那么这个文件的长度就要变长,同时这个文件的建立日期和时间也要改成当前的日期和时间。这些信息和文件名一起组成了文件的目录项,这个目录项要在文件处理完后存入磁盘的目录表中。而这个工作同样由"关闭文件"功能完成。
大家可以做这样一个试验:将一个文件拷贝到两个软盘上,编程打开其中一片软盘上的文件,将一些数据写入这个文件,然后让程序暂停等待按键。这时将第一片软盘取出而将第二片软盘放入驱动器,按一键让程序启动执行关闭文件功能并结束。这时候就会发现第二片软盘上的那个文件发生了一些变化,文件很可能无法使用了。造成这个现象的原因就是DOS在执行"关闭文件"功能时并不知道盘片更换了,由于这片盘上有一个同名文件,所以DOS会把特征信息存入这个同名的目录项中,从而导致了错误。由此可见"关闭文件"操作十分重要,是文件处理中不可忽视的一个环节。
新的程序固然有了正确的输出,但目的文件的长度和源文件不一致。这个原因就很简单了,我们读入的数据都会有"零头",除非被读取的文件恰好有整数个记录。而写出数据时不会有"零头",总是要将一个记录的数据完整写出。因此目的文件的长度总要长一些,而且目的文件的长度恰好是记录长度的整数倍。比如我的CONFIG文件复制后长度由667个字节变为768个字节,恰好是6个记录长。
解决这个问题的方法是在数据数据全部写出后人为修改目的文件的FCB,将其中的文件长度设为667字节,然后再关闭文件。这样DOS在更新目录项时就会将正确的文件长度写入磁盘目录表中。FCB8.ASM是彻底修改后的程序:
;数据段定义与程序FCB7.ASM相同,此处省略 CODE SEGMENT ASSUME CS:CODE MAIN PROC FAR MOV AX,DATA ;初始化DS寄存器 MOV DS,AX MOV AH,0FH ;打开FCB1指示的文件 MOV DX,OFFSET FCB1 INT 21H OR AL,AL ;文件打开正确吗? JNZ ERR_EXIT ;打开不正确,转ERR_EXIT结束 MOV AH,0FH ;打开FCB2指示的文件 MOV DX,OFFSET FCB2 INT 21H OR AL,AL ;文件打开正确吗? JZ READ_REC ;文件正确打开,转READ_REC MOV AH,16H ;新建由FCB2指示的文件 MOV DX,OFFSET FCB2 INT 21H OR AL,AL ;文件正确地建立了吗? JNZ ERR_EXIT ;文件没有正确建立,转ERR_EXIT READ_REC: MOV AH,14H ;从FCB1指示的文件中读入一个记录 MOV DX,OFFSET FCB1 INT 21H OR AL,AL ;数据正确地读入了吗? JZ L_TO_U ;数据已经读入,转L_TO_U CMP AL,3 ;读入部分数据吗? JNZ ERR_EXIT ;是其它错误,转ERR_EXIT L_TO_U: MOV DI,80H ;DI寄存器指向DTA MOV CX,128 ;转换128个字符 NEXT_CHAR: MOV AL,ES:[DI] ;取得一个字符 CMP AL,'A' ;是"A"吗? JB NOT_UPPER ;若比"A"小,转NOT_UPPER CMP AL,'Z' ;是"Z"吗? JA NOT_UPPER ;比"Z"大,转NOT_UPPER ADD AL,20H ;将AL寄存器中的字符转换成小写 NOT_UPPER: STOSB ;将转换成的小写字母存回DTA LOOP NEXT_CHAR ;处理下一个字符 MOV AH,15H ;将转换完毕的数据写入FCB2指示的文件中 MOV DX,OFFSET FCB2 INT 21H OR AL,AL ;写入操作正确吗? JNZ ERR_EXIT ;没有正确地的写入文件,转ERR_EXIT JMP READ_REC ;处理下一个记录 ERR_EXIT: MOV BX,OFFSET FCB1 ;BX指向FCB1 MOV AX,WORD PTR [BX+10H] ;AX寄存器取得文件长度低字 MOV DX,WORD PTR [BX+12H] ;DX寄存器取得文件长度高字 MOV BX,OFFSET FCB2 ;BX指向FCB2 MOV WORD PTR [BX+10H],AX ;将"源文件"的长度填入FCB2 MOV WORD PTR [BX+12H],DX MOV AH,10H ;关闭FCB2指示的文件 MOV DX,OFFSET FCB2 INT 21H MOV AH,4CH ;结束进程 INT 21H MAIN ENDP CODE ENDS END MAIN
我们已经讨论了有关FCB文件处理的五项系统功能,灵活的使用这5项功能就可以编制出非常有效的程序。当然DOS还给我们提供了一些辅助性的功能调用,那么这些功能我在此就不再过多讨论,学习也是要分主次的,真正掌握住主要的东西是学习好汇编语言的关键。那么下面我们就要讨论FCB记录的随机存取功能。