在计算机科学技术中,常用“文件”这一术语来表示输入输出操作的对象。所谓“文件”,是指记录在外部介质上的集合。例如用Word或Excel编辑制作的文档或表格就是一个文件,把它存放在磁盘上就是一个磁盘文件,输出到打印机上就是一个打印机文件。
文件通常存放在磁盘上,通过“路径”指明它在磁盘上的位置。“路径”是由目录(也称文件夹)和文件名组成。
1、文件说明与文件结构
文件是信息的集合,每个文件都有一个区别于其它文件的名字。下面先介绍如何对数据文件命名,然后再介绍VB中文件的一般结构和种类。
(1)文件说明
所谓文件说明,指的是文件的命名规则。在VB中,文件说明的一般格式为:
设备名:文件引用名
例如:A:myfile.abc
这里的“A:”是设备名,而“myfile.abc”是文件引用名。
设备名是存放文件设备的名称,如磁盘、磁带、打印机等。在微机中,存放文件的主要设备是磁盘,其名称通常为A:,B:,C:,D:等,分别A,B,C,D驱动器。
文件引用名由两部分组成,即文件基本名和扩展名。其中文件基本名以字母开头(在DOS环境下,最多不超过8个字符,在Windows环境下,不超过255个字符);扩展名是可选的,最多不超过3个字符。
下面字符可以做文件基本名和扩展名:
-
英文26个字母(大小写均可);
-
数字(0-9);
-
特殊字符($ # & @ ! % () - _ ' ^ ~ / )。
完整的文件说明由设备名和文件引用名构成。对于磁盘文件,还可以含有路径。例如:
c:/textfile/sprot.tex
另外,文件说明不区分大小写。
(2)文件结构和分类
文件结构
为了有效地存取数据,数据必须以某种特定的方式存放,这种特定的方式称为文件结构。
VB的文件由记录组成,记录由字段组成,字段又由字符组成。
字符:是构成文件的最基本单位。字符可以是数字、字母、特殊符号或单一字节。这里说的字符一般为西文字符,一个西文字符用一个字节存放。如果为汉字字符包括汉字和全角字符,则通常由两个字节存放。也就是说一个汉字字符相当于两个西文字符。一般把用一个字节存放的西文字符称为“半角”字符,而把汉字和用两个字符存放的字符称为“全角”字符。但VB支持双字节字符,在计算字符串长度时,一个西文字符和一个汉字都作为一个字符计算,只是它们所占的内存空间不一样。例如“VB程序设计”的长度为6,而所占的字节数为10。
字段:也称域。字段由若干个字符组成,用来表示一项数据。例如邮政编码“100859”就是由6个字符组成的一个字段。而姓名“张三”也是一个字段,由2个汉字组成。
记录:由一组相关的字段组成。例如在通信录中,每个人的姓名、单位、地址、电话号码、邮政编码等构成一个记录。在VB中,以记录为单位处理数据。
文件:文件由记录构成,一个文件含有一个以上记录。
文件分类
在计算机中,文件随着分类标准的不同可分为不同的类型。
按照文件的存取方式及其组成结构来分可以分为两种类型:顺序文件、随机文件;
顺序文件:结构较简单,文件中的记录一个接一个地存放。在这种文件中,只知道第一个记录的存放位置,其它记录的位置无从知道。当要查找某个数据时,只能从文件头开始,一个记录一个记录地顺序读取,直到找到为止。这种类型的文件组织比较简单,占空间少,容易使用,但维护困难,适用于有一定规律且不经常修改的数据。
随机文件:又称直接存取文件,简称随机文件或直接文件。随机文件的每个记录都有一个记录号,在写入数据时只要指定记录号,就可以把数据直接存入指定位置。而在读取数据时,只要给出记录号,就可直接读取。在记录文件中,可以同时进行读、写操作,所以能快速地查找和修改每个记录,不必为修改某个记录而像顺序文件那样,对整个文件进行读、写操作。其优点是数据存取较为灵活、方便,速度快,容易修改,主要缺点是占空间较大,数据组织复杂。
按照文件的数据编码方式来分可以分为ASCII码文件和二进制文件;
ASCII码文件:又称文本文件。它以ASCII方式保存文件,可用字处理软件建立和修改(必须以纯文本文件保存)。
二进制文件:不能用普通的字处理软件编辑,占空间较小。
按照文件的特征属性来分可以分为系统文件、隐藏文件、只读文件、普通文件和存档文件;
按照文件的数据性质来分可以分为程序文件和数据文件。
程序文件:这种文件存放的是可以由计算机执行的程序,包括源文件和可执行文件。在VB中,扩展名为.exe,.frm,.vbp,.vbg,.bas,.cls等的文件都是程序文件。
数据文件:数据文件用来存放普通的数据,例如学生的考试成绩、职工工资、商品库存等。这类数据必须通过程序来存取和管理。
2、文件的打开与关闭
在VB中,数据文件的操作按下述步骤进行:
打开(或建立)文件
一个文件必须先打开或建立后才能使用。如果一个文件已存在,则打开该文件;如果不存在,则建立该文件。
进行读、写操作
在打开(或建立)的文件上执行所要求的输入输出操作。在文件处理中,把内存中的数据传输到相关联的外部设备(如磁盘)并作为文件存放的操作叫做写数据,而把数据文件中的数据传输到内存程序中的操作叫做读数据。一般来说,在主存与外设的数据传输中,由主存到外设叫做输出或写;而由外设到主存叫做输入或读。
关闭文件
文件处理一般需要以上三步。在VB中,数据文件的操作通过有关的语句和函数来实现。
下表列出了三种直接文件访问类型的各种文件访问的所有语句和函数。
语句与功能 | 顺序型 | 随机型 | 二进制型 |
Open | X | X | X |
Get |
| X | X |
Input() | X |
| X |
Input# | X |
|
|
Line Input# | X |
|
|
Print # | X |
|
|
Put |
| X | X |
Type..End Type |
| X |
|
Write # | X |
|
|
Close | X |
| X |
顺序的访问是为普通的文本文件的使用而设计的。文件中每一个字符都被假设为代表一个文本字符或者文本格式序列,比如换行符(NL)。数据被存储为ASCII字符。
而为随机型访问打开的文件则认为是由相同长度的记录集合组成。可用记录类型来创建由各种各样的字段组成的记录,每个字段可以有不同的数据类型。
数据作为二进制信息存储。
二进制访问允许使用文件来存储所希望的数据。除了没有数据类型或者记录长度的含义外,它与随机访问很相似。
3、顺序文件
所谓的顺序文件,通常指的是普通的文本文件,文件中的字符包括文本字符及控制字符如",","chr(14)","cr,lf" ,"chr(10)等。
由于这类型文件的信息是按照顺序从头到尾按顺序排列,而且只提供第一条记录的存储位置,所以对其中的数据信息进行处理是相当困难的。
(1)顺序文件的打开
VB中用Open语句打开或建立一个文件。
打开顺序文件的语法结构如下:
Open 文件说明 [For 方式] [Access 存取类型] [lock] As [#] 文件号 [Len=记录长度]
Open语句的功能是:为文件的输入输出分配缓冲区,并确定缓冲区所使用的存取方式。
Open语句的语法参数及其说明:
格式中的Open、For、Access、As及Len为关键字。
文件说明(Pathname):如前面所述,指定了文件路径和文件名。
方式(Mode):指定文件输入、输出方式。有Append,Binary,Input,Output或Random方式。
方式是可选的,如果省略,则为随机存取方式,即Random方式。
- Output:指定顺序输出方式。
- Input:指定顺序输入方式。
- Append:指定顺序输出方式,与Output不同的是当用Append方式打开文件时,文件指针被定位在文件末尾。如果对文件执行写操作,则写入的数据附加到原来文件的后面。
- Random:指定随机存取方式,这是默然方式。在这种方式中,如果没有Access子句,则在执行Open语句时,VB按下列顺序打开文件:读/写、只读、只写。
- Binary:指定二进制方式文件。在这种方式中,如果没有Access子句,则打开文件的类型与Random方式相同。
存取类型(Access):用来指定访问文件的类型,有Read、Write或ReadWrite操作。
- Read:打开只读文件。
- Write:打开只写文件。
- ReadWrite:打开读/写文件。这种文件只对随机文件、二进制文件及Append方式打开的文件有效。
锁定(Lock):该子句只在多用户或多进程环境中使用,用来限制其他用户或其他进程对打开的文件进行读写操作。有Lock Shared、Lock Read、Lock Write和Lock Read Write操作。
- 默认:如不指定锁定类型,则本进程可以多次打开文件进行读写;在文件打开期间,其他进程不能对该文件执行读写操作。
- Lock Shared:任何机器上的任何进程都可以对该文件进行读写操作。
- Lock Read:不允许其他进程读该文件。只在没有其他Read存取类型的进程访问该文件时,才允许这种锁定。
- Lock Write:不允许其他进程写该文件。只在没有其他Write存取类型的进程访问该文件时,才允许这种锁定。
- Lock Read Write:不允许其他进程读写该文件。
如果不使用Lock子句,则默认为Lock Read Write。
文件号(Filenumber):是一个整形表达式,其值在1到511之间。执行Open语句时,打开文件的文件号与一个具体的文件相关联,其他输入输出语句或函数通过文件号与文件发生联系。
记录长度(Reclength):是一个整形表达式,是小于或等于32767(字节)的一个数。当选择该参量时,为随机存取文件设置记录长度。对于用随机访问方式打开的文件,该值就是记录长度,对于顺序文件,该值就是缓冲字符数,对于二进制文件,将忽略Len子句。
在顺序文件中,“记录长度”不需要与各个记录的大小相对应,因为顺序文件各个记录的长度可以不相同。当打开顺序文件时,在把记录写入磁盘或从磁盘读出记录之前,“记录长度”指出要装入缓冲区的字符数,即确定缓冲区的大小。缓冲区越大,占用空间越大,文件的输入输出操作越快。默认时缓冲区的容量为512字节。
特别说明:
- 为了满足不同的存取方式的需要,对同一个文件可以用几个不同的文件号打开,每个文件号有自己的一个缓冲区。对于不同的访问方式,可以使用不同的缓冲区。但是,当使用Output或Append方式时,必须先将文件关闭,才能重新打开文件。而当使用Input、Random或Binary方式时,不必关闭文件就可以用不同的文件号打开文件。
- Open语句兼有打开建立文件两种功能。在对一个数据文件进行读、写、修改或增加数据之前,必须先用Open语句打开或建立该文件。如果为输入(Input)打开的文件不存在,则产生“文件未找到”错误;如果为输出(Output)、附加(Append)或随机(Random)访问方式打开的文件不存在,则建立相应的文件。此外,在Open语句中,任何一个参数的值如果超出给定的范围,则产生“非法功能调用”的错误,而且文件不能被打开。
下面的示例就是利用Open语句中参数的不同设置来做到文件的输出与输入。
- 下列代码以顺序输入模式打开C:/windows/win.ini文件
Open "C:/windows/win.ini" For Input As #1 '打开已存在的数据文件,以便从文件中读出记录。
close#1
- 下列代码只允许写操作的二进制方式打开C:/windows/win.ini文件
Open "C:/windows/win.ini" For Binary Access Write As #1 '以二进制方式打开只写文件。
close#1
- 下列代码以顺序输出方式打开C:/windows/win.ini文件
Open "C:/windows/win.ini" For Output Lock Shared As #1 '如果文件存在,则打开该文件(如果不存在,则建立该文件),使记录可以写到该文件中,新写入的记录将覆盖原来的记录。
close#1
- 下列代码只允许读的二进制方式打开C:/windows/win.ini文件
Open "C:/windows/win.ini" For Binary Access Read Lock Read As #1 '若要以其他方式打开文件,必须先关闭此文件
close#1
- 下列代码只允许读写的二进制方式打开C:/windows/win.ini文件
Open "C:/windows/win.ini" For Binary Access Read Write Lock Read Write As #1 '若要以其他方式打开文件,必须先关闭此文件
close#1
(2)顺序文件的文件的关闭
文件的读写操作结束后,应将文件关闭,这可以通过Close语句实现。其格式为:
Close[[#]文件号][,[#]文件号]……
说明:
Close语句用来关闭文件,它是在打开文件之后进行的操作。格式中的“文件号”是Open语句中使用的文件号。关闭一个数据文件具有两方面的作用,第一,把文件缓冲区中的所有数据写到文件中;第二,释放与该文件相关联的文件号,以供其它Open语句使用。
Close语句中的“文件号”是可选的。如果指定了文件号,则把指定的文件关闭;如果不指定文件号,则把所有打开的文件全部关闭。
除了用Close语句关闭文件外,在程序结束时将自动关闭所有打开的数据文件。
Close语句使VB结束对文件的使用。它的操作十分简单,但绝不是可有可无的,这是因为磁盘文件同内存之间的信息交换是通过缓冲区进行的。如果关闭的是为顺序输出而打开的文件,则缓冲区中最后的内容将被写入文件中。当打开的文件或设备正在输出时,执行Close语句后,不会使输出信息的操作中断。如果不使用Close语句关闭文件,则可能使某些需要写入的数据不能从内存(缓冲区)送入文件中。
(3)顺序文件的写操作
前面讲过,数据文件的写操作分三步进行,即打开文件、写入文件和关闭文件。其中打开文件和关闭文件分别由Open和Close语句来实现,写入文件由Print #或Write #语句来完成。
Print #语句
Print #的功能是:把数据写入文件中。它与Print方法的功能类似,只不过Print方法所“写”的对象是窗体、打印机或图片框,而Print #语句所“写”的对象是文件。
Print #语句格式:
Print # 文件号,[[Spc(n)│Tab(n)][表达式表][;│,]]
例如:Print # 1,A,B,C
把变量A,B,C的值写到文件号为1的文件中。
如果是Print A,B,C
则把变量A,B,C的值“写”到窗体上。
说明:
1)“文件号”的含义同前,数据被写入该文件号所代表的文件中。
2)其它参量,包括Spc函数、Tab函数、“表达式表”及尾部的分号、逗号等与Print方法中相同。如“表达式表”也可以省略,如果省略,将向文件中写入一个空行。分号和逗号分别对应紧凑格式和标准格式。但值得说明的是,在使用分号时,如果数据是数值,由于数值数据前有符号位,后有空格,所以使用分号不会给以后读取数据造成麻烦,但是如果数据是字符串,特别是变长字符串,用分号分隔就有可能引起麻烦,因为输出的字符串数据之间没有空格。例如,设:
A="Beijing",B="Shanghai",C="Tianjin"
则执行:
Print # 1,A;B;C
后,写到磁盘上的信息为“BeijingShanghaiTianjin”。为了使输出的各字符串明显的分开,可以人为的插入逗号,即改为:
Print # 1,A;",";B;",";C
这样写入文件的信息为:“Beijing,Shanghai,Tianjin”。
3)实际上,Print #语句的任务只是将数据送到缓冲区,而数据由缓冲区写到磁盘文件的操作是由文件系统来完成的。对于用户来说,可以理解为由Print #语句直接将数据写入磁盘文件。但是执行Print #语句后,并不是立即把缓冲区中的内容写入磁盘,只有满足下列条件之一时才写盘:
-
关闭文件(Close)。
-
缓冲区已满。
-
缓冲区未满,但执行下一个Print #语句。
例1:
Private Sub Form_Click()
Open "c:/temp/tel.dat" For Output As #1
Tpname = InputBox("请输入姓名:", "数据输入")
Tptel = InputBox("请输入电话号码:", "数据输入")
TpAddr = InputBox("请输入地址:", "数据输入")
Print #1, Tpname, Tptel, TpAddr
Close #1
End Sub
这个过程的执行是:先在C盘的temp目录下打开一个名为tel.dat的输出文件(如果没有该文件,则建立一个),文件号为1,然后分别输入姓名、电话号码、地址,最后关闭。
注意:这里的“temp”文件夹要存在,如果没有,可以先在C盘下建立,当然,也可以用其它名字。可用“记事本”查看。
Write #语句
格式:
Write # 文件号,[表达式表]
功能:
和 Print #语句一样,用Write #可以把数据写入顺序文件中。例如:
Write # 1,A,B,C
将把变量A,B,C的值写入文件号为1的文件中。
说明:
1)“文件号”和“表达式表”的含义同前。当使用Write #语句时,文件必须以Output或Append方式打开。“表达式表”中的各项以逗号分开。
2)Write # 语句与Print #语句的功能基本相同,其主要区别有以下两点:
- 当用Write # 语句向文件写数据时,数据在磁盘上以紧凑格式存放,能自动地在数据项之间插入逗号,并给字符串加上双引号。一旦最后一项被写入,就插入新的一行。
- 用Write # 语句写入的正数的前面没有空格。
例2:
Private Sub Form_Click()
Open "c:/Temp/tel1.dat" For Output As #1
unit = InputBox("Enter unit:")
While UCase(unit) <> "DONE" '当输入done时退出循环。
tel = InputBox("Telephone number:")
Write #1, unit, tel
Write #1, '写入一空行。
nowdate = #4/28/2004#
Write #1, nowdate '写入日期。
unit = InputBox("Enter unit:")
Wend
Close #1
End
End Sub
可将该例与例1比较。
3)如果试图用Write #语句把数据写到一个用Lock语句限定的顺序文件中去,则会发生错误。
用Open语句建立的顺序文件是ASCII文件,可以用字处理程序来查看或修改。顺序文件由记录组成,每个记录是一个单一的文本行,它以回车换行序列结束。每个记录又被分成若干个字段,这些字段是记录中按同一顺序反复出现的数据块。在顺序文件中,每个记录可以具有不同的长度,不同记录中的字段的长度也可以不一样。
当把一个字段写入变量时,存储字段的变量的类型决定了该字段的开头和结尾。如果把字段存入字符串变量,则下列符号标示该字符串的结尾:
- 双引号(""):当字符串以双引号开头时;
- 逗号(,):当字符串不以双引号开头时;
- 回车-换行:当字段位于记录的结束处时。
如果把字段写入数据变量,则下列符号标示该字段的结尾:
- 逗号;
- 一个或多个空格;
- 回车-换行。
例3:从键盘上输入4个学生的数据,然后把它们存放到磁盘文件中。
学生的数据包括姓名、学号、年龄、住址,用一个记录类型来定义。按下述步骤编写程序:
在标准模块中定义如下记录类型:
Type stu
stname As String * 10
num As Integer
age As Integer
addr As String * 20
End Type
在窗体层输入如下代码:
Option Base 1
编写如下的窗体事件过程:
Private Sub Form_Click()
Static stud() As stu
Open "c:/Temp/stu_list" For Output As #1
n = InputBox("Enter number of student:")
ReDim stud(n) As stu
For i = 1 To n
stud(i).stname = InputBox("Enter stud " & i & " Name:")
stud(i).num = Val(InputBox("Enter stud " & i & " Number:"))
stud(i).age = Val(InputBox("Enter stud " & i & " Age:"))
stud(i).addr = InputBox("Enter stud " & i & " Address:")
Write #1, stud(i).stname, stud(i).num, stud(i).age, stud(i).addr
Next i
Close #1
End
End Sub
程序运行后,从键盘上输入学生人数如4个,分别输入他们的姓名、学号、年龄、住址。
在字处理软件中查看内容为:
"王大力 ",9901,19,"3号楼202室 "
"张 红 ",9902,20,"3号楼204室 "
"李 明 ",9903,21,"3号楼205室 "
"向 阳 ",9904,18,"3号楼208室 "
可以看出,由于用Write #语句执行写操作,文件中各数据项用逗号分开,且字符串放在双引号中。
(4)顺序文件的读操作
顺序文件的读操作分三步进行,即打开文件、读数据文件和关闭文件。其中打开、关闭文件的操作如前所述,读数据的操作由Input #语句和Line Input #实现。
Input #语句
格式:
Input # 文件号,变量表
功能:
Input #语句从一个顺序文件中读出数据项,并把这些数据项赋给程序变量。例如:
Input #1,A,B,C
从文件中读出3个数据项,分别把它们赋给A,B,C三个变量。
说明:
1)“文件号”的含义同前。“变量表”由一个或多个变量组成,这些变量既可以是数值变量,也可以是字符串变量或数组元素,从数据文件中读出的数据赋给这些变量。文件中的数据项的类型应与Input #语句中变量的类型匹配。
2)在用Input #语句把读出的数据赋给数值变量时,将忽略前导空格、回车或换行符,把遇到的第一个非空格、非回车和换行符作为数值的开始,遇到空格、回车或换行符则认为数值结束。对于字符串数据,同样忽略开头的空格、回车或换行符。如果需要把开头带有空格的字符串赋给变量,则必须把字符串放在双引号中。
3)Input #与InputBox函数类似,但InputBox要求从键盘上输入数据,而Input #语句要求从文件中输入数据,而且执行Input #语句时不显示对话框。
4)Input #也可用于随机文件中。
例4:把前面建立的学生数据文件(stu_list)读到内存,并在屏幕(窗体)上显示出来。
该程序标准模块不变,窗体层代码也一样,只是窗体事件过程为:
Private Sub Form_Click()
Static stud() As stu
Open "c:/Temp/stu_list" For Input As #1
n = InputBox("Enter number of student:")
ReDim stud(n) As stu
Print "姓 名"; Tab(20); "学 号"; Tab(30); "年龄"; Tab(40); "地址"
Print
For i = 1 To n
Input #1, stud(i).stname, stud(i).num, stud(i).age, stud(i).addr
Print stud(i).stname; Tab(20); stud(i).num; Tab(30); stud(i).age; Tab(40); stud(i).addr
Next i
Close #1
End Sub
运行结果为:
例5:见教材P356。利用Print #语句从一个文件中读取数据,然后把它显示在窗体上。
Private Sub Form_Click()
Dim Str1 As String
Open "c:/windows/win.ini " For Input As #1
Do While Not EOF(1)
Input #1, Str1
Form1.Print Str1
Loop
Close #1
End Sub
关于EOF函数: 功能:用来测试文件的结束状态。 格式:EOF(文件号) 利用EOF函数,可以避免在文件输入时出现“输入超出文件尾”的错误。即在文件输入期间,可以用EOF测试是否到达文件尾。对于顺序文件,如果已到文件末尾,则EOF函数返回True,否则返回false;对于随机文件和二进制文件,如果最后执行的Get语句未能读到一个完整的记录,则返回True,这通常发生在试图读文件结尾以后的部分时。 EOF函数常用来在循环中测试是否已到文件尾,一般结构为: Do While Not EOF(1) 文件读写语句 Loop |
Line Input #语句
格式:
Line Input # 文件号,字符串变量
功能:
Line Input #语句从一个顺序文件中读取一个完整的行,并把它赋给一个字符串变量。
说明:
1)“文件号”的含义同前。“字符串变量”是一个字符串简单变量名,也可以是一个字符串数组元素名,用来接收从顺序文件读出的字符串。
2)在文件操作中,Line Input #是十分有用的语句,它可以读取顺序文件一行的全部内容,直至遇到回车符为止。此外,对于以ASCII码存放在磁盘上的各种语言源程序,都可以用Line Input #语句一行一行地读取。
Line Input #与Input #语句功能类似,只是Input #语句读取的是文件中的数据项,而Line Input #语句读取的是文件中的一行。
3)Line Input #语句也可用于随机文件。
Line Input #语句常用来复制文本文件,见下例:
例6:把一个磁盘文件的内容读到内存并在文本框中显示出来,然后把该文本框中的内容存入另一个磁盘文件。
首先用字处理软件(例如“记事本”)建立一个名为“Smtext1.txt”的文件(该文件存放在C盘的Temp目录下),内容如下:
经五丈原
铁马云雕共绝尘,柳营高压汉宫春。
天清杀气屯关右,夜半妖星照渭滨。
下国卧龙空寤主,中原得鹿不由人。
象床宝帐无言语,从此谯周是老臣。
该文件有6行,输入时,每行均以回车键结束。
在窗体上建立一个文本框,MultiLine属性设置为True,然后编写如下代码:
Private Sub Form_Click()
Open "c:/Temp/smtext1.txt" For Input As #1
Text1.FontSize = 12
Text1.FontName = "黑体"
Do While Not EOF(1)
Line Input #1, aspect
whole = whole + "" + aspect + Chr(13) + Chr(10)
Loop
Text1.Text = whole
Close #1
Open "c:/Temp/smtext2.txt" For Output As #1
Print #1, Text1.Text
Close #1
End Sub
程序说明:
上述过程首先打开一个磁盘文件smtext1.txt,用Line Input #语句把该文件的内容一行一行地读到变量aspect中,每读一行,就把该行连到变量whole,加上回车换行符。然后把变量whole的内容放到文本框中,并关闭该文件。,此时文本框将分行显示smtext1.txt的内容。之后,程序建立一个名为smtext2.txt的文件,并把文本框的内容写入该文件。所以程序运行结束后,文本框及两个磁盘文件具有相同的内容。
运行后的窗体界面为:
Input函数
格式:
Input(n,#文件号)
功能:
Input函数返回从指定文件中读出的n个字符的字符串。也就是说,它可以从数据文件中读取指定数目的文件。例如:
x=Input(100,#1)
从文件号为1的文件中读取100个字符,并把它赋给变量x。
Input函数执行所谓“二进制输入”。它把一个文件作为非格式的字符流来读取。例如,它不把回车-换行序列看作是一次输入操作的结束标志。因此,当需要用程序从文件中读取单个字符时,或者是用程序读取一个二进制的或非ASCII码文件时,使用Input函数较为适宜。
例7:编写程序,在文件中查找指定的字符串。
为了查找文件中指定的字符串,可以先打开文件,用Input(1,1)搜索要查找的字符串的首字符,再试着进行完整的匹配。更直观的做法是:把整个文件读入内存,放到一个变量中,然后从这个变量中查找所需要的字符串。在VB中,一个字符串变量最多可存放20亿个字符。
首先用字处理软件(例如“记事本”)建立一个名为“xyz.txt”的文件(该文件存放在C盘的Temp目录下),作为练习,可以随便输入。
然后在窗体中添加如下程序:
Private Sub Form_Click()
Q = InputBox("请输入要查找的字符串:")
Open "c:/Temp/xyz.txt" For Input As #1
X = Input(LOF(1), 1) '把整个文件内容读入变量X中。
Close #1
y = InStr(1, X, Q)
If y <> 0 Then
Print "找到字符串"; Q
Else
Print "未找到字符串"; Q
End If
End Sub
关于LOF函数: 功能:返回给文件分配的字节数(即文件的长度),与在DOS下用dir命令所显示的数值相同。 格式:LOF(文件号) |
2、随机文件
随机文件有以下特点:
1)随机文件的记录是定长记录,只有给出记录号n,才能通过[(n-1)×记录长度]计算出该记录与文件首记录的相对地址。因此在用Open语句打开文件时,必须指定记录的长度。
2)每个记录划分为若干个字段,每个字段的长度等于相应的变量的长度。
3)各变量(数据项)要按一定格式置入相应的字段。
4)打开随机文件后,既可读也可写。
随机文件与顺序文件的读写操作类似,但通常把需要读写的记录中的各字段放在一个记录类型中,同时应指定每个记录的长度。
(1)随机文件的写操作
随机文件的写操作分为4步:
1)定义数据类型
随机文件由固定长度的记录组成,每个记录含有若干个字段。可以把记录中的各个字段放在一个记录类型中,记录类型用用Type……End Type定义。前面已经看到Type……End Type在标准模块中使用。
2)打开随机文件
与顺序文件不同,打开一个随机文件后,既可用于写操作,又可用于读操作。打开随机文件的一般格式为:
Open "文件名称" For Random As #文件号[Len=记录长度]
“记录长度”等于各字段长度之和,以字符(字节)为单位。如果省略“Len=记录长度”,则默认长度为128字节。
3)将内存中的数据写入磁盘
随机文件的写操作通过Put语句来实现。其格式为:
Put #文件号,[记录号],变量
这里的“变量”是除对象变量和数组变量外的任何变量(包括含有单个数组元素的下标变量)。Put语句把“变量”的内容写入由“文件号”所指定的磁盘文件中。
说明:
“文件号”的含义同前。“记录号”的取值范围为1-221-1,即1-2 147 483 647。对于用Random方式打开的文件,“记录号”是需要写入的编号。对于用Binary方式打开的文件(见后),“记录号”是开始写的字节位置。文件中的第一个字节位置为1,第二个字节位置为2,依次类推。如果省略“记录号”,则写到下一个记录或字节位置,即最近执行Get或Put语句后或由最近的Seek函数(见后)所指定的位置。但省略“记录号”后,逗号不能省略。例如:
Put #2,,Filebuff
如果所写的数据的长度小于在Open语句的Len子句中所指定的长度,Put语句仍然在记录的边界后写入后面的记录,当前记录的结尾和下一个记录开头之间的空间用文件缓冲区现有的内容填充。由于填充数据的长度无法确定,因此最好使记录长度与要写的数据的长度相匹配。
如果要写入的变量是一个变长字符串,则除写入变量外,Put语句还写入2个字节的一个描述符,因此,由Len子句所指定的记录长度至少应比字符串的实际长度多2个字节。
如果要写入的变量是一个可变数值类型变量(VarType值0到7),则除写入变量外,Put语句还要写入两个字节用来标记变体变量的ValType。例如,当写入ValType为3的变体时,Put语句要写入6个字节:其中2个字节用来把变体标记为3(Long型),4个存放Long型数据。因此,在Len子句中指出的记录长度至少应比存放变量所需要的实际长度多2个字节。
如果要写入的是字符串变体(VarType 8),则Put语句要写入2个字节用来标记VarType,2个字节用来标记字符串的长度。在这种情况下,由Len子句指定的记录长度至少应比字符串的实际长度多4个字节。
如果要写入的是其他类型的变量(即非变长字符串或变体类型),则Put语句只写入变量的内容,由Len子句所指定的记录长度应大于或等于所要写的数据的长度。
4)关闭文件
关闭文件的操作与顺序文件相同。
(2)随机文件的读操作
从随机文件中读取数据的操作与写文件操作步骤类似,只是把第三步中的Put语句用Get语句来代替。其格式为:
Get #文件号,[记录号],变量
Get语句把由“文件号”所指定的磁盘文件中的数据读到“变量”中。
“记录号”的取值范围同前,它是要读的记录的编号。如果省略“记录号”,则读取下一个记录,即最近执行Get或Put语句后的记录,或由最近的Seek函数指定的记录。同样,省略“记录号”后,逗号不能省略。例如:
Get #1,,Filebuff
Get语句的其它说明,包括“文件号”、“变量”的含义以及在读变长字符串、变体等变量时的一些规则,均与Put语句类似,不再重复。
下面通过一个例子说明随机文件的读写操作。
例8:建立一个随机存取的工资文件,然后读取文件中的记录。
为了便于说明问题,使用如下表所示的简单的文件结构。
姓名 | 单位 | 年龄 | 工资 |
按以下步骤操作:
1)定义数据类型
工资文件的每个记录含有4个字段,其长度(字节数)及数据类型见下表。
项 目 | 长 度 | 类 型 |
姓名(Emname) | 10 | 字符串 |
单位(Unit) | 15 | 字符串 |
年龄(Age) | 2 | 整型数 |
工资(Salary) | 4 | 单精度数 |
根据上面规定的字段长度和数据类型,定义记录类型。
在标准模块中定义如下记录类型:
Type RecordType
EmName As String * 10
Unit As String * 15
Age As Integer
Salary As Single
End Type
在窗体层定义该类型的变量:
Dim Recordvar As RecordType
2)打开文件,并指定记录长度
从前面可知,要建立的随机文件的每个记录的长度为10+15+2+4=31个字节,因此用下面的语句打开文件:
Open "Employee.dat" For Random As #1 Len=31
用上面的语句打开文件时,记录的长度通过手工计算得到。当记录含有的字段较多时,这种手工计算很不方便,也容易出错。实际上,记录类型变量的长度就是记录的长度,可以通过Len函数求出来,即:
记录长度=Len(记录类型变量)
=Len(Recordvar)
因此,打开文件的语句可以改为:
Open "Employee.dat" For Random As #1 Len=Len(Recordvar)
注意这里的两个Len的意义是不同的,等号左边的Len是Open语句中的子句,等号右边的Len是一个函数。
3)从键盘上输入记录中的各个字段,对文件进行读写操作
打开文件后,就可以输入数据,并把数据记录写入磁盘文件。程序如下:
Recordvar.EmName = InputBox("职工姓名:")
Recordvar.Unit = InputBox("所在单位:")
Recordvar.Age = InputBox("职工年龄:")
Recordvar.Salary = InputBox("职工工资:")
RecordNumber = RecordNumber + 1
Put #1, , Recordvar
用上面的程序段可以把一个记录写入磁盘文件Employee.dat。把这段程序放在循环中,就可以把指定数量的记录写入文件中。不必关闭文件,就可以从文件中读取记录。例如:
Get #1,,Recordvar
4)关闭文件
以上是建立和读取工资文件的一般操作,在具体编写程序时,应设计好文件的结构。
下面是完整的程序。
在标准模块中定义如下记录类型:
Type RecordType
EmName As String * 10
Unit As String * 15
Age As Integer
Salary As Single
End Type
在窗体层定义该记录类型变量和其它变量:
Dim Recordvar As RecordType
Dim Position As Integer
Dim RecordNumber As Integer
编写如下通用过程,执行输入数据及写盘操作:
Sub File_Write()
Do
Recordvar.EmName = InputBox("职工姓名:")
Recordvar.Unit = InputBox("所在单位:")
Recordvar.Age = InputBox("职工年龄:")
Recordvar.Salary = InputBox("职工工资:")
RecordNumber = RecordNumber + 1
Put #1, RecordNumber, Recordvar
aspect = InputBox("More(Y/N)?")
Loop Until UCase(aspect) = "N"
End Sub
随机文件中读数据操作:
随机文件建立后,可以从文件中读取数据。从随机文件中读数据有两种方法:一种是顺序读取,一种是通过记录号读取。由于顺序读取不能直接访问任意指定的记录,因而速度较慢。
编写如下通用过程,执行顺序读文件操作:
Sub File_Read1()
Cls
FontSize = 12
For i = 1 To RecordNumber
Get #1, i, Recordvar
Print Recordvar.EmName, Recordvar.Unit,
Print Recordvar.Age, Recordvar.Salary, Loc(1)
Next i
End Sub
该过程从前面建立的随机文件Employee.dat中顺序地读出全部记录,从头到尾读取,并在窗体上显示出来。
关于Loc函数: 格式:LOC(文件号) 功能:返回由“文件号”指定的文件的当前读写位置。格式中的“文件号”是在Open语句中使用的文件号。 |
随机文件的主要优点之一,就是可以通过记录号直接访问文件中任一个记录,从而可以大大提高存取速度。在用Put语句向文件写记录时,就把记录号赋给了该记录。在读取文件时,通过把记录号放在Get语句中可以从随机文件取回一个记录。
下面是通过记录号读取随机文件Employee.dat中任一记录的通用过程:
Sub File_Read2()
Getmorerecords = True
Cls
FontSize = 12
Do
Recordnum = InputBox("Enter record number for part you want to see(0 to end):")
If Recordnum > 0 And Recordnum <= RecordNumber Then
Get #1, Recordnum, Recordvar
Print Recordvar.EmName; ""; Recordvar.Unit; "";
Print Recordvar.Age; ""; Recordvar.Salary; ""; Loc(1)
MsgBox "单击“确定”按钮继续"
ElseIf Recordnum = 0 Then
Getmorerecords = False
Else
MsgBox "Input value out of range,re-enter it"
End If
Loop While Getmorerecords
End Sub
该过程在Do……Loop循环中要求输入要查找的记录号。如果输入的记录号在指定的范围内,则在窗体上输出相应记录的数据;当输入的记录号为0时结束程序;如果输入的记录号不在指定的范围内,则显示相应的信息,并要求重新输入。
上述三个通用过程分别用来建立随机文件、用顺序方式或通过记录号读取文件记录。
在下面的窗体事件过程中调用这三个过程:
Private Sub Form_Click()
Open "c:/temp/employee.dat" For Random As #1 Len = Len(Recordvar)
RecordNumber = LOF(1) / Len(Recordvar)
Newline = Chr(13) + Chr(10)
msg$ = "1.建立文件"
msg$ = msg$ + Newline + "2.顺序方式读记录"
msg$ = msg$ + Newline + "3.通过记录号读文件"
msg$ = msg$ + Newline + "4.删除记录"
msg$ = msg$ + Newline + "0.退出程序"
msg$ = msg$ + Newline + Newline + "请输入数字选择:"
Begin:
resp = InputBox(msg$)
Select Case resp
Case 0
Close #1
End
Case 1
File_Write
Case 2
File_Read1
MsgBox "单击“确定”按钮继续"
Case 3
File_Read2
Case 4
'Deleterec
End Select
GoTo Begin
End Sub
上述程序运行后,单击窗体,在显示的输入对话框(如下图)中输入0-3(暂不输入4)之间的数字,即可调用相应的通用过程执行随机文件的读写操作。
该程序可以执行4种操作,即写文件、顺序读文件、通过记录号读文件和删除文件中指定的记录。其中删除记录的操作将在后面介绍。
(3)随机文件中记录的增加与删除
1)增加记录
在随机文件中增加记录,实际上是在文件的末尾附加记录。其方法是,先找到文件最后一个记录的记录号,然后把要增加的记录写在它的后面。
前面的通用过程File_Write具有建立文件和增加记录两种功能。也就是说,如果打开一个已经存在的文件,则写入的新记录将附加到该文件后面。
运行前面的程序,出现对话框后键入1,并单击“确定”按钮,然后再输入几个记录数据。
输入结束后,回到选择对话框,键入2并“确定”后,将在窗体上显示文件中的所有记录,可以看出新增加的记录已附在原来记录的后面。
2)删除记录
在随机文件中删除一个记录时,并不是真正删除记录,而是把下一个记录重写到要删除的记录上,其后的所有记录依次前移。例如前面建立的文件有4个记录(为便于操作,简单的输入):
张三 办公室 35 850
李四 财务处 32 780
王五 供销科 36 810
徐六 人事科 42 890
假定要删除第二个记录“李四 财务处 32 780”。其方法是:将第三个记录写到第二个记录上,第四个记录写到第三个记录上,其内容变为:
张三 办公室 35 850
王五 供销科 36 810
徐六 人事科 42 890
徐六 人事科 42 890
文件中仍有4个记录,原来的第二个没有了,最后两个记录相同。也就是说,最后一个记录是多余的。为了解决这个问题,可以把原来的记录个数减1,由4个变为3个。这样,当再向文件中增加记录时,多余的记录即被覆盖。
根据上面分析,编写删除记录的通用过程如下:
Sub Deleterec(position As Integer) '参数position是要删除的记录的记录号
repeat:
Get #1, position + 1, Recordvar
If Loc(1) > RecordNumber Then GoTo finish
Put #1, position, Recordvar
position = position + 1
GoTo repeat
finish:
RecordNumber = RecordNumber - 1
End Sub
上述过程用来删除文件中的指定的记录,参数position是要删除的记录的记录号。该过程用后面的记录覆盖前面要删除的记录,并将其后的记录依次前移,移动完成后,最后的记录号减1。
该过程是前面程序的一部分,可以在前面的事件过程中调用。把事件过程中的“Case 4”后面的部分改为:
p = InputBox("输入记录号:")
Deleterec(p)
运行程序后,在选择对话框中输入4,将显示一个对话框,要求输入要删除的记录的记录号。输入2并单击“确定”按钮,第二个记录即被删除,回到选择对话框。此时如果输入2,单击“确定”按钮,即可看到第二个记录已被删除。
(4)用控件显示和修改随机文件
前面介绍了随机文件的读写及修改操作。执行这些操作的程序是用VB编写的,然而其操作方法和显示方法与传统语言没什么区别。如果能结合VB的控件来显示和修改随机文件中的数据,效果要好得多。下面通过一个例子说明它的实现方法。
例9:编写程序,用控件显示和修改随机文件中的数据。
我们仍然使用前面建立的“employee.dat”文件来设计程序。按以下步骤操作:
1)在标准模块中定义记录类型(同前)。
2)在窗体模块中定义变量(同前)。
3)在窗体上添加控件,并设置属性,如下:
![]() | ![]() |
其中4个文本框是一个文本框控件数组的4个元素,其控件组名是Text1,并将其Text属性设置为空白。
4)编写代码
编写通用过程:
Sub display()
Get #1, position, Recordvar
Text1(0).Text = Recordvar.EmName
Text1(1).Text = Recordvar.Unit
Text1(2).Text = Recordvar.Age
Text1(3).Text = Recordvar.Salary
End Sub
编写事件过程:
打开文件:
Private Sub Command3_Click()
Open "c:/temp/employee.dat" For Random As #1 Len = Len(Recordvar)
RecordNumber = LOF(1) / Len(Recordvar)
position = 1
display
End Sub
该过程用来打开文件,计算出最后一个记录的记录号,并把当前记录号设置为1,然后调用display过程显示记录的内容。
显示前一个记录:
Private Sub Command1_Click()
If position > 1 Then
position = position - 1
display
ElseIf position = 1 Then
MsgBox "这是第一个记录"
End If
End Sub
该事件过程用来显示当前记录的前一个记录。由于第一个记录没有“前一个记录”,因此如果当前记录是第一个记录,则单击“前一记录”按钮时将显示相应的信息。
显示下一个记录:
Private Sub Command2_Click()
If position < RecordNumber Then
position = position + 1
display
ElseIf position = RecordNumber Then
msg$ = "这是最后一个记录!" + Chr(13) + Chr(10)
msg$ = msg$ + "是否关闭文件?"
resp = MsgBox(msg$, 36, "请选择")
If resp = 6 Then
Close #1
End
End If
End If
End Sub
该事件过程用来显示当前记录的下一个记录,如果当前记录是最后一个记录,则单击“下一记录”按钮时将显示相应的信息,并询问用户是否关闭文件。如果单击对话框中的“Yes”按钮,则关闭文件,结束程序运行,否则继续运行。
修改记录:
Private Sub Command4_Click()
Recordvar.EmName = Text1(0).Text
Recordvar.Unit = Text1(1).Text
Recordvar.Age = Text1(2).Text
Recordvar.Salary = Text1(3).Text
Put #1, position, Recordvar
End Sub
该事件过程用来修改记录。单击需要修改的字段所在的文本框,把光标移到该文本框中,就可修改该文本框的内容。修改后单击“数据修改”命令按钮,就可以把修改后的记录写入文件中,并覆盖原来的记录。
退出程序:
Private Sub Command5_Click()
End
End Sub
3、二进制文件
利用二进制存取可以获取任一文件的原始字节,即不仅能获取ASCII文件,而且能获取非ASCII文件的原始字节。这样,用户就可以读取或修改以非ASCII格式存盘的文件,例如可执行文件(.exe)
用下面的语句可以打开二进制输入/输出文件:
Open 文件说明 For Binary As #文件号
为二进制存取打开的文件作为非格式化的字节序列处理。可以把记录读或写到以二进制方式打开的文件中,但文件本身不必处理成定长记录。实际上,二进制文件本身不必涉及记录,除非把文件中的每个字节看成一个记录。
通过使用二进制型访问可使磁盘空间的使用降到最小。因为二进制文件不需要固定长度的字段(在二进制文件中每个记录只占用所需字节,所以不必为字段指定长度),类型声明语句可以省略字符串长度参数。当记录中的字段需要包含大段文字时,使用二进制文件可以节省大量的磁盘空间。
(1)二进制存取与随机存取
二进制文件与随机文件的存取操作类似,这主要表现在以下两个方面:
1)不需要在读与写之间切换。在执行Open语句打开文件后,对该文件既可以读,也可以写,并且利用二进制存取可以在一个打开的文件中前后移动。
2)读写随机文件的语句也可以用于读写二进制文件,即:
Get│Put #文件号,[位置],变量
在这里,“变量”可以是任何类型,包括变长字符串和记录类型;“位置”指明下一个Get或Put操作在文件的什么地方进行。二进制文件中的“位置”相对于开头而言。即第一个字节的“位置”是1,第二个字节的“位置”是2,等等。如果省略“位置”,则Get和Put操作将文件指针从第一个字节到最后一个字节顺序进行扫描。
Get语句从文件中读出的字节数等于“变量”的长度;同样Put语句向文件中写入的字节数与“变量”的长度相同。例如,如果“变量”为整型,则Get语句就把读取的2个字节赋给“变量”;如果“变量”为单精度型,则Get语句就读取4个字节。因此,如果Get和Put语句中没有指定“位置”,则文件指针每次移过一个与“变量”长度相同的距离。
二进制文件与随机文件也有不同之处,主要是:二进制存取可以移到文件中的任一字节位置上,然后根据需要读、写任意个字节;而随机存取每次只能移到一个记录的边界上,读取固定个数的字节(一个记录的长度)。
二进制文件只能通过Get语句或Input#函数读取数据,而Put则是向以二进制方式打开的文件中写入数据的唯一方法。
为了可以同时使用随机文件和二进制文件的优点,可以使用这样的组合--当字段的长度固定或者长度变化不大时,可以将这些字段存储在随机文件中,而对于长度变化很大的字段,可以保存在二进制文件中,而在随机文件的记录中设置一个字段指定它在二进制文件中的位置即可。这样,既可以利用随机文件的方便快捷,又可以大大节省许多磁盘空间。
(2)文件指针
在二进制文件中,可以把文件指针移到文件中任意位置。文件指针的定位通过Seek语句来实现。其格式为:
Seek #文件号,位置
Seek语句用来设置文件中下一个读或写的位置。“文件号”的含义同前,“位置”是一个数值表达式,用来指定下一个要读写的位置,其值在1—231-1的范围内。
说明:
1)对于用Binary,Input,Output或Append方式打开的文件,“位置”是从文件开头到“位置”为止的字节数,即执行下一个操作的地址,文件第一个字节的位置为1。对于用Random方式打开的文件,“位置”是一个记录号。
2)在Get或Put语句中的记录号优先于由Seek语句确定的位置。此外,当“位置”为0或负数时,将产生出错信息“错误的记录号”。当Seek语句中的“位置”在文件尾之后时,对文件的写操作将扩展该文件。
与Seek语句配合的使用的是Seek函数。其格式为:
Seek(文件号)
该函数返回文件指针的当前位置。由Seek函数返回的值在1—231-1的范围内。
对于用Binary,Input,Output或Append方式打开的文件,Seek函数返回文件中的字节位置(产生下一个操作的位置),对于用Random方式打开的文件,Seek函数返回下一个要读或写的记录号。
在访问二进制文件时,Seek函数与Loc函数给出相似的结果。所不同的是Loc函数返回的是最近一次读写过的字节的位置,而Seek函数返回的则是下一次要读或写的字节位置。
对于顺序文件,Seek语句把文件指针移到指定的字节位置上,Seek函数返回有关下次将要读写的位置信息;对于随机文件,Seek语句只能把文件指针移到一个记录的开头,而Seek函数返回的是下一个记录号。
例10:编写程序,建立一个二进制文件,然后用Seek函数返回各项数据的位置。
在窗体上添加3个命令按钮,Caption属性分别为“建立文件”、“返回位置”和“退出”,然后对3个按钮编写如下事件过程:
“建立文件”事件过程:
Private Sub Command1_Click()
Dim stuname As String * 10
FileName$ = InputBox("Enter file name:")
Open FileName For Binary As #1
Do
stuname = InputBox("请输入学生的姓名:")
Put #1, , stuname
resp$ = InputBox("继续输入吗?(Y/N)")
Loop While UCase(resp) = "Y"
Close #1
End Sub
该过程要求输入一个文件名,并把它作为二进制文件打开(该文件如不指定路径,则默认位置在VB98文件夹下,可以用记事本查看),然后向该文件中写入数据(学生姓名)。每输入完一个姓名,程序询问是否继续输入,键入“Y”继续,否则结束输入。
“返回位置”事件过程:
Private Sub Command2_Click()
Dim stuname As String * 10
FileName$ = InputBox("Enter file name:")
Open FileName For Binary As #1
flen = LOF(1)
For i = 1 To flen Step 10
Get #1, , stuname
x = Seek(1)
Print stuname, x
Next i
Close #1
End Sub
Seek函数返回的是下一个要读出的字节位置。由于变量的长度为10个字节,因此第一次返回的字节位置为11,第二次为21,依次类推。
“退出”事件过程:
Private Sub Command3_Click()
End
End Sub