一).一般问题
二进制文件与我们通常使用的文本文件储存方式有根本的不同。这样的不同很难用言语表达,自己亲自看一看,理解起来会容易得多。因此,我推荐学习二进制文件读写的朋友安装一款十六进制编辑器。这样的编辑器有很多,在我们的
CVF
附带的集成开发环境下就可以(将二进制文件拖动到
IDE
窗口后松开)。Visual
Studio
2005
也是可以的。(不过需要在
File
菜单下
Open,File)
另外推荐一款使用较多的软件,叫做
UltraEdit(以下简称
UE)。是很不错的文本编辑器,也能做十六进制编辑器使用。
为什么要用十六进制编辑器?而不用
2
进制呢?因为
2
进制实在太小,书写起来会很长,很不直观。而我们的计算机把
8
位作为一个字节。刚好
2
**
8
=
256
=
16
**
2。用
8
位
2
进制表达的数,我们用
2
个十六进制数据来表达,更直观和方便。
二).文件格式
所有文件,笼统意义上将可以区分为两类,一类是文本文件,一类是二进制文件。
1).文本文件
文本文件用记事本等文本编辑器打开,我们可以看懂上面的信息。所以使用比较广泛。通常一个文本文件分为很多很多行,作为数据储存时,还有列的概念。实际上,储存在硬盘或其他介质上,文件内容是线一样储存的,列是用空格或
Tab
间隔,行是用回车和换行符间隔。
以
ANSI
编码(使用较多)的文本文件来说,例如我们储存如下信息:
需要的空间是:3
行
×
每行
2
个字符
+
2
个回车符
+
2
个换行符
=
10
字节。文本文件储存数据是有格式,无数据类型的。比如
10
这个数据,并不指定是整型还是实型还是字符串。它有长度,就是
2,两个字节。储存时计算机储存它的
ASCII
码:31h,30h。(十六进制表示)。回车符是:0Dh,换行符:0Ah。
因此,这个数据储存是这样的:
(红色为回车符和换行符)
31h
30h
就是
10,31h
31h
就是
11,31h
32h
就是
12。因此我们也可以认为文本文件是特殊的二进制文件。
2).二进制文件
二进制文件,是无格式有数据类型的。比如上面的
10
11
12
三个数。但二进制文件没有行的概念。我们要紧凑地储存他们。(当然也可以中间加入一些空白的字节)
从数据类型上来说,我们首先考虑整型。如果把
10
11
12
当作
2
字长的整型。则
10
表示为:0Ah
00h。因为
0Ah
对应十进制
10。而后面的
00h
是空白位。2
字长的整型如果不足
FFh,也就是不足
255,则需要一个空白位。类似的:11
表示为
0Bh
00h,12
表示为
0Ch
00h。
当整型数据超过
255
时,我们需要
2
个字节来储存。比如
2748(ABCh),则表示为:BCh
0Ah。要把低位写在前面(BCh),高位写在后面(0Ah)。
当整型数据超过
65535
时,我们就需要
4
个字节来储存。比如
439041101(1A2B3C4Dh),则表示成:4Dh
3Ch
2Bh
1Ah。当数据再大时,我们就需要
8
字节储存了。
二进制文件的实型数据也有字节长度的区分,比如
4
字长,8
字长。但实型数据的长度并不仅仅代表它的表达的范围,更多的代表精度。所以,8
字长的我们又称为双精度。关于实型数据如何储存为
2
进制。则有很多套规则。现在都广泛使用的是
IEEE
标准浮点格式。关于这样的规则,我还正在了解,比较麻烦。就不多说了。在这里也没有必要了解。
二进制文件也可以储存字符型数据,储存方法和文本文件一样。都是使用
ASCII
编码储存的。所以我们用记事本打开某些二进制文件时,也能看到一些有意义的字符串。(无意义的乱码我们可以认为是整型或实型,不过记事本程序当作字符来解释,因此造成了乱码)
三).使用二进制文件的好处
为什么要使用二进制文件。原因大概有三个:
第一是二进制文件比较节约空间,这两者储存字符型数据时并没有差别。但是在储存数字,特别是实型数字时,二进制更节省空间,比如储存
Real*4
的数据:3.1415927,文本文件需要
9
个字节,分别储存:3
.
1
4
1
5
9
2
7
这
9
个
ASCII
值,而二进制文件只需要
4
个字节(DB
0F
49
40)
第二个原因是,内存中参加计算的数据都是用二进制无格式储存起来的,因此,使用二进制储存到文件就更快捷。如果储存为文本文件,则需要一个转换的过程。在数据量很大的时候,两者就会有明显的速度差别了。
第三,就是一些比较精确的数据,使用二进制储存不会造成有效位的丢失。
四).二进制文件的储存方式
列举一个二进制文件如下:
这里列出的是在
UltraEdit(UE)
里看到的东西。其实只有红色部分是文件内容。前面的是
UE
加入的行号。后面的是
UE
尝试解释为字符型的参考。
这个文件一共有
32
字节长。显示为两列,每列
16
个字节。实际上,这仅仅是
UE
的显示而已。真实的文件并不分行。仅仅知道这个文件的内容,如果我们没有任何说明的话,是不能看出任何有用信息的。
下面我规定一下说明:我们认为,前
4
个字节是一个
4
字节的整型数据(0F
01
00
00
十六进制:10Fh
十进制:271)。这
4
个字节之后的
4
个字节是另一个
4
字节的整型数据(0F
03
00
00
十六进制:30Fh
十进制:783)。其后的
4
个字节(12
53
21
45
)表示一个
4
字节的实型数据:2.5811919E+3。再其后的
4
个字节(58
62
35
34)表示另一个
4
字节的实行数据:1.6892716E-7。而只后的
16
个字节(41
42
43
44
45
46
47
48
49
47
4B
4C
4D
4E
4F
50)我们认为是
16
个字节的字符串(ABCDEFGHIGKLMNOP)
实际上,二进制文件只是储存数据,并不写明数据类型,比如上面的第
9
字节到第
16
字节(12
53
21
45
58
62
35
34),我们刚才认为是
2
个
4
字节的实型,其实也可以认为是
8
个字节的字符型( S!EXb54)。而后面的
16
个字节的字符串(ABCDEFGHIGKLMNOP),我们也可以认为是
2
个
8
字节的整型,或者
4
个
4
字节的整型,甚至
2
个
8
字节的实型,4
个
4
字节的实型,等等等等。
因此,面对一个二进制文件,我们不能准确地知道它的含义,我们需要他的数据储存方式的说明。这个说明告诉我们第几个字节到第几个字节是什么类型的数据,储存的数据是什么含义。否则的话,我们只能猜测,或者无能为力。
五).如何使用语句操作二进制文件
我们将上面的那个二进制文件保存为:TestBin.Bin
来举例。
读取和写入二进制其实是两个很类似的操作,了解了其中之一,另一个也就不难了。
二进制文件我们通常使用直接读取方式,Open
语句可以写为:
上面的
Access
表示直接读取方式,Form
表示无格式储存。比较重要的是
RecL
。我们读取数据时,是用记录来描述单位的,每一次读入或写入是一个记录。记录的长度在
Open
时就确定下来,以后不能改变。如果需要改变,只能
Close
以后再此
Open。
记录长度在某些编译器下表示读取的
4
字节长度的倍数,规定为
4
表示记录长度为
16
字节。有些编译器下就直接表示记录的字节数,规定为
4
则表示记录长度为
4
字节。这个问题需要参考编译器手册。在
VF
系列里,这个值是前面一个含义。可以通过设置工程属性的
Fortran,Data,Use
Bytes
as
RECL=
Unit
for
Unformatted
Files
来改变,使之成为后一个含义。在命令行模式下,则使用
/assume:byterecl
这个编译选项。
确定
RecL
大小是我们需要做的事情,一般来说,不适合太大,也不适合太小。还需要结合数据储存方式来考虑。太小的话,我们需要执行读写的次数就多,太大的话,我们就不方便操作小范围的数据。
有时候我们甚至会分多次来读取数据,每一次的
RecL
都不同。对于上面的
TestBin.Bin
文件来说,它比较简单,我以
16
字节长度和
8
字节长度两种读取方式来演示,你甚至可以一次
32
个字节长度全部读完:
(1)RecL = 4 【记录长度 16 字节】
这里的
Open
里指定了
RecL
=
4(记录长度是
16
字节)。
第一个
Read
语句,直接读取第二笔记录(也就是第
17
字节到第
32
字节)。读取出的
cStr
=
"ABCDEFGHIGKLMNOP"。
第二个
Read
语句,返回来读取第一笔记录(也就是前面
16
个字节)。读取出的数据分别放入
4
个
4
字节的变量。(其中前面两个是整型,后面两个是实型)
输出结果为:
ABCDEFGHIGKLMNOP
271
783
2581.192
1.6892716E-07
看到这个结果,就说明我们成功了。
同时我们可以看到,第一个语句,我们直接跳到第二条记录读取,并没有读取第一条。这就是直接读取数据的方便。有时候我们根本不需要某些数据,这时候,我们可以直接跳到某一条记录上。这个记录甚至可以是我们实现算出来的变量。比如:
iRec
=
(
a
+
b
)
/
C
Read(
12
,
Rec
=
iRec
)
cStr
实现我们储存了
100
天的数据,我们只需要第
21
天的数据,我们怎么办?在顺序读取时,我们可能会开辟一个
100
元素的数组,或者循环执行
20
次空白的读取。但是在直接读取时,我们只需要执行一句
Read(
12
,
Rec
=
21
)。这是多么的方便。(直接读取和顺序读取虽然于文本文件和二进制文件没有直接的关联,但是文本文件通常用顺序读取,而二进制文件通常用直接读取。这是他们的性质决定的。)
(2)RecL = 2【记录长度为 8 字节】
这里设定的
RecL
=
2
,意思是一笔记录
8
个字节。所以我们不能一次读取
cStr
这个
16
字节的字符串。我们必须分两次读取。第一次读取第
4
笔记录,放入字符串后半段。第二次读取第
3
笔记录,放入字符串前半段。(可以调换位置)。然后读取第一笔记录的两个整型变量和第二笔记录的两个实型变量。
输出结果和(1)的方法一样。
(3)写入二进制文件
写入二进制文件同样需要考虑
RecL
的问题。我们这里以
RecL
=
4
来举例。
写入二进制文件和读取二进制文件是差不多的,我就不再解释了。需要注意的是,如果直接写入第
N
笔记录,而文件没有只有
M
笔记录(M
<
N),那么,第
M+1
到第
N-1
笔记录会用
0
填充。也就是说,二进制文件不会出现断裂。
二进制文件的读写是比较灵活的,实际应用中,我们使用哪种方式,我们应该根据自己的情况来设计。如何选择合适的记录长度
RecL,如何设计高效的储存方式等。
二进制文件与我们通常使用的文本文件储存方式有根本的不同。这样的不同很难用言语表达,自己亲自看一看,理解起来会容易得多。因此,我推荐学习二进制文件读写的朋友安装一款十六进制编辑器。这样的编辑器有很多,在我们的
另外推荐一款使用较多的软件,叫做
为什么要用十六进制编辑器?而不用
二).文件格式
所有文件,笼统意义上将可以区分为两类,一类是文本文件,一类是二进制文件。
文本文件用记事本等文本编辑器打开,我们可以看懂上面的信息。所以使用比较广泛。通常一个文本文件分为很多很多行,作为数据储存时,还有列的概念。实际上,储存在硬盘或其他介质上,文件内容是线一样储存的,列是用空格或
以
引用:10
11
12
需要的空间是:3
引用:3130 0D 0A 31 31 0D 0A 31 32
(红色为回车符和换行符)
二进制文件,是无格式有数据类型的。比如上面的
从数据类型上来说,我们首先考虑整型。如果把
当整型数据超过
当整型数据超过
二进制文件的实型数据也有字节长度的区分,比如
二进制文件也可以储存字符型数据,储存方法和文本文件一样。都是使用
三).使用二进制文件的好处
为什么要使用二进制文件。原因大概有三个:
第一是二进制文件比较节约空间,这两者储存字符型数据时并没有差别。但是在储存数字,特别是实型数字时,二进制更节省空间,比如储存
第二个原因是,内存中参加计算的数据都是用二进制无格式储存起来的,因此,使用二进制储存到文件就更快捷。如果储存为文本文件,则需要一个转换的过程。在数据量很大的时候,两者就会有明显的速度差别了。
第三,就是一些比较精确的数据,使用二进制储存不会造成有效位的丢失。
四).二进制文件的储存方式
引用:00000000h:0F 01 00 00 0F 03 00 00 12 53 21 45 58 62 35 34 ; .........S!EXb54
00000010h:41 42 43 44 45 46 47 48 49 47 4B 4C 4D 4E 4F 50 ; ABCDEFGHIGKLMNOP
这里列出的是在
这个文件一共有
下面我规定一下说明:我们认为,前
实际上,二进制文件只是储存数据,并不写明数据类型,比如上面的第
因此,面对一个二进制文件,我们不能准确地知道它的含义,我们需要他的数据储存方式的说明。这个说明告诉我们第几个字节到第几个字节是什么类型的数据,储存的数据是什么含义。否则的话,我们只能猜测,或者无能为力。
五).如何使用语句操作二进制文件
我们将上面的那个二进制文件保存为:TestBin.Bin
读取和写入二进制其实是两个很类似的操作,了解了其中之一,另一个也就不难了。
二进制文件我们通常使用直接读取方式,Open
引用:Open(12 , File = 'TestBin.Bin' , Access = 'Direct' , Form = 'Unformatted' , RecL = 4 )
上面的
记录长度在某些编译器下表示读取的
确定
有时候我们甚至会分多次来读取数据,每一次的
(1)RecL
引用:Programmain
Implicit None
Integer*4 :: iVar1 , iVar2
Real*4 :: rVar1 , rVar2
Character(Len=16) :: cStr
Open( 12 , File = 'TestBin.Bin' , Access = 'Direct' , Form = 'Unformatted' , RecL = 4 )
Read( 12 , Rec = 2 ) cStr
Read( 12 , Rec = 1 ) iVar1 , iVar2 , rVar1 , rVar2
Write( * , * ) cStr
Write( * , * ) iVar1 , iVar2 , rVar1 , rVar2
Close( 12 )
EndProgram main
这里的
第一个
第二个
输出结果为:
看到这个结果,就说明我们成功了。
同时我们可以看到,第一个语句,我们直接跳到第二条记录读取,并没有读取第一条。这就是直接读取数据的方便。有时候我们根本不需要某些数据,这时候,我们可以直接跳到某一条记录上。这个记录甚至可以是我们实现算出来的变量。比如:
iRec
引用:Programmain
Implicit None
Integer*4 :: iVar1 , iVar2
Real*4 :: rVar1 , rVar2
Character(Len=16) :: cStr
Open( 12 , File = 'TestBin.Bin' , Access = 'Direct' , Form = 'Unformatted' , RecL = 2 )
Read( 12 , Rec = 4 ) cStr( 9 : 16 )
Read( 12 , Rec = 3 ) cStr( 1 : 8 )
Read( 12 , Rec = 1 ) iVar1 , iVar2
Read( 12 , Rec = 2 ) rVar1 , rVar2
Write( * , * ) cStr
Write( * , * ) iVar1 , iVar2 , rVar1 , rVar2
Close( 12 )
EndProgram main
这里设定的
输出结果和(1)的方法一样。
(3)写入二进制文件
写入二进制文件同样需要考虑
引用:Programmain
Implicit None
Open( 12 , File = 'TestBinW.Bin' , Access = 'Direct' , Form = 'Unformatted' , RecL = 4 )
Write( 12 , Rec = 1 ) 271 , 783 , 2581.192_4 , 1.6892716E-07
Write( 12 , Rec = 2 ) "ABCDEFGHIGKLMNOP"
Close( 12 )
EndProgram main
写入二进制文件和读取二进制文件是差不多的,我就不再解释了。需要注意的是,如果直接写入第
二进制文件的读写是比较灵活的,实际应用中,我们使用哪种方式,我们应该根据自己的情况来设计。如何选择合适的记录长度