爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 12715|回复: 29

[经验总结] 详解Fortran二进制读写

[复制链接]

新浪微博达人勋

发表于 2014-6-15 17:52:09 | 显示全部楼层 |阅读模式

登录后查看更多精彩内容~

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x
本帖最后由 /xin村儿/ 于 2014-6-20 08:42 编辑


摘自:http://bbs.pfan.cn/post-256197.html  “此贴来自于某人的博客,觉得还是贴到自己博客上比较保险,不容易丢”.....  原文如下:

‘我’看到的最珍贵的一句话:二进制文件读取的最关键问题是,数据的类型要符合
以什么格式写入的数据,就要以什么格式来读取,不要相信FORTRAN里一些默认的功能。
当用“顺序读写”来读取数据时,要注意此时二进制文件的操作到了那个位置。比如:
先打开一个新的文件,然后顺序写入数据,然后再读取已写入的数据。对于顺序读取,非常关键的一个问题就是,在写完数据后要把文件先关闭,否则后面的操作将是从文件末尾开始的,也就无法继续了。这个问题折磨了我一天,终于解决了。
下面是一些关于二进制读写的在网上搜到的东西,供参考:

Fortran中二进制文件的顺序读写
所有文件,笼统意义上将可以区分为两类,一类是文本文件,一类是二进制文件。
使用二进制文件的好处大概有三个:
  第一是二进制文件比较节约空间,这两者储存字符型数据时并没有差别。但是在储存数字,特别是实型数字时,二进制更节省空间,比如储存 Real*4 的数据:3.1415927,文本文件需要 9 个字节,分别储存:3 . 1 4 1 5 9 2 7 这 9 个 ASCII 值,而二进制文件只需要 4 个字节(DB 0F 49 40)
  第二是,内存中参加计算的数据都是用二进制无格式储存起来的,因此,使用二进制储存到文件就更快捷。如果储存为文本文件,则需要一个转换的过程。在数据量很大的时候,两者就会有明显的速度差别了。
  第三,就是一些比较精确的数据,使用二进制储存不会造成有效位的丢失。

范例:
Program main
Implicit None
integer :: i_int,stat_int
integer  :: a_int
real(kind=8) :: b_real,c_real
character(len=100) :: filename_char
LOGICAL  :: alive_logical
filename_char = 'TestBinW.Bin'
!打开一个文件,form='BINARY' 或者 form='UNFORMATED' 表示打开的是二进制文件
!access="SEQUENTIAL" 表示是顺序读取
!status="replace",如果文件存在则覆盖,不存在则新建
!action='Write' 表示要对文件进行写操作

Open( 12 , File = filename_char ,form='BINARY', access="SEQUENTIAL",&
status="replace",action='Write')
!向文件中先写入一个四字节的整型数(默认为4),在分别写入四个八字节的双精度实型数
Write( 12 ) 2
Write( 12 ) 3.0d0, 4.0d0
Close( 12 )
filename_char = 'TestBinW.Bin'
!查询文件是否存在, EXIST返回值为1,存在, 为0,不存在
inquire(file=filename_char,EXIST=alive_logical)
if(alive_logical ==0) then
write(*,*) trim(filename_char),'  does not exist'
stop
endif

!打开文件,二进制,顺序读写,只读。
Open(12, File = filename_char,form='BINARY', access="SEQUENTIAL",&
status="old",action='read')
!先读一个整型数
read(12) a_int
write(*,*)a_int
!再每次读取两个实型数
do while(1)
read(12,IOSTAT=stat_int) b_real,c_real
!如果是文件尾或读取出错,则终止
if(stat_int .ne. 0) stop
write(*,*)b_real,c_real
end do
End Program main

总结:
二进制文件读取的最关键问题是,数据的类型要符合。
比如一开始存进去的四个字节是用来表示整型数a_int的。
如果读取的时候,先读取b_real,c_real, 再读取a_int的话,则会出错。
例如,把读取那段换成
read(12,IOSTAT=stat_int) b_real,c_real
write(*,*)b_real,c_real
read(12) a_int
write(*,*)a_int

然后再查看输出结果。


Fortran 二进制文件读写【给新手】
2008/05/17 11:43

  一些朋友总是咨询关于二进制文件的读写和转化。这里就我自己的理解说一说。

一).一般问题
  二进制文件与我们通常使用的文本文件储存方式有根本的不同。这样的不同很难用言语表达,自己亲自看一看,理解起来会容易得多。因此,我推荐学习二进制文件读写的朋友安装一款十六进制编辑器。这样的编辑器有很多,在我们的 CVF 附带的集成开发环境下就可以(将二进制文件拖动到 IDE 窗口后松开)。Visual Studio 2005 也是可以的。(不过需要在 File 菜单下 Open,File)
  另外推荐一款使用较多的软件,叫做 UltraEdit(以下简称 UE)。是很不错的文本编辑器,也能做十六进制编辑器使用。
  为什么要用十六进制编辑器?而不用 2 进制呢?因为 2 进制实在太小,书写起来会很长,很不直观。而我们的计算机把 8 位作为一个字节。刚好 2 ** 8 = 256 = 16 ** 2。用 8 位 2 进制表达的数,我们用 2 个十六进制数据来表达,更直观和方便。

二).文件格式
  所有文件,笼统意义上将可以区分为两类,一类是文本文件,一类是二进制文件。

1).文本文件
  文本文件用记事本等文本编辑器打开,我们可以看懂上面的信息。所以使用比较广泛。通常一个文本文件分为很多很多行,作为数据储存时,还有列的概念。实际上,储存在硬盘或其他介质上,文件内容是线一样储存的,列是用空格或 Tab 间隔,行是用回车和换行符间隔。
  以 ANSI 编码(使用较多)的文本文件来说,例如我们储存如下信息:
10
11
12

  需要的空间是:3 行 × 每行 2 个字符 + 2 个回车符 + 2 个换行符 = 10 字节。文本文件储存数据是有格式,无数据类型的。比如 10 这个数据,并不指定是整型还是实型还是字符串。它有长度,就是 2,两个字节。储存时计算机储存它的 ASCII 码:31h,30h。(十六进制表示)。回车符是:0Dh,换行符:0Ah。
    因此,这个数据储存是这样的:

31 30 0D 0A 31 31 0D 0A 31 32
  (红色为回车符和换行符) 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)
  第二个原因是,内存中参加计算的数据都是用二进制无格式储存起来的,因此,使用二进制储存到文件就更快捷。如果储存为文本文件,则需要一个转换的过程。在数据量很大的时候,两者就会有明显的速度差别了。
  第三,就是一些比较精确的数据,使用二进制储存不会造成有效位的丢失。

四).二进制文件的储存方式

    列举一个二进制文件如下:
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
  这里列出的是在 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 语句可以写为:

Open( 12 , File = 'TestBin.Bin' , Access = 'Direct' , Form = 'Unformatted' , RecL = 4 )
  上面的 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 字节】
Program main
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 )
End Program main

  这里的 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 字节】
Program main
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 )
End Program main

  这里设定的 RecL = 2 ,意思是一笔记录 8 个字节。所以我们不能一次读取 cStr 这个 16 字节的字符串。我们必须分两次读取。第一次读取第 4 笔记录,放入字符串后半段。第二次读取第 3 笔记录,放入字符串前半段。(可以调换位置)。然后读取第一笔记录的两个整型变量和第二笔记录的两个实型变量。
  输出结果和(1)的方法一样。

  (3)写入二进制文件
  写入二进制文件同样需要考虑 RecL 的问题。我们这里以 RecL = 4 来举例。

Program main
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 )
End Program main

  写入二进制文件和读取二进制文件是差不多的,我就不再解释了。需要注意的是,如果直接写入第 N 笔记录,而文件没有只有 M 笔记录(M < N),那么,第 M+1 到第 N-1 笔记录会用 0 填充。也就是说,二进制文件不会出现断裂。

  二进制文件的读写是比较灵活的,实际应用中,我们使用哪种方式,我们应该根据自己的情况来设计。如何选择合适的记录长度 RecL,如何设计高效的储存方式等。











评分

参与人数 3金钱 +30 贡献 +7 体力 +40 收起 理由
mofangbao + 15 + 3
lqouc + 10 + 4 + 40 不错哈,很多人的提问都是这里不清楚。
言深深 + 5

查看全部评分

密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2014-6-15 20:15:18 | 显示全部楼层
这个东西好像在什么地方见过
密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2014-6-15 20:15:30 | 显示全部楼层
这个东西好像在什么地方见过
密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2014-6-15 20:46:41 | 显示全部楼层
不错哈,楼主一下分享了不少东西,其实可以再提炼一下下哈,争取把学的别人的变成你原创的。
话说上面的内容在彭国伦的书里也有很全面细致的解释。有的时候可以看看书,网上的东西难免有谬误(不是说你转的这个有错哈)。
ps:冷色调的字体背景看着有点晕
密码修改失败请联系微信:mofangbao

新浪微博达人勋

 成长值: 0
发表于 2014-6-15 20:46:43 | 显示全部楼层
对于fortran而言,默认功能确实是害死人的。
会用,则如鱼得水,不会用,当真是一头雾水、错误不断。
密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2014-6-15 21:05:11 | 显示全部楼层
言深深 发表于 2014-6-15 20:46
对于fortran而言,默认功能确实是害死人的。
会用,则如鱼得水,不会用,当真是一头雾水、错误不断。

深深版主~我的收藏的帖子怎么都木有了?以前不都是从快捷导航里面打开的,现在我一点快捷导航发现我的收藏里面社么都没有啊~难道是我打开的方式不对?
密码修改失败请联系微信:mofangbao

新浪微博达人勋

 楼主| 发表于 2014-6-15 21:36:52 | 显示全部楼层
lqouc 发表于 2014-6-15 20:46
不错哈,楼主一下分享了不少东西,其实可以再提炼一下下哈,争取把学的别人的变成你原创的。
话说上面的内 ...

改成暖色调了,分享于此是为了方便以后查看
密码修改失败请联系微信:mofangbao

新浪微博达人勋

 楼主| 发表于 2014-6-15 21:37:37 | 显示全部楼层
qxtlyf 发表于 2014-6-15 20:15
这个东西好像在什么地方见过

给了出处 的哈
密码修改失败请联系微信:mofangbao

新浪微博达人勋

 成长值: 0
发表于 2014-6-16 09:16:44 | 显示全部楼层
sun_shine_Xia 发表于 2014-6-15 21:05
深深版主~我的收藏的帖子怎么都木有了?以前不都是从快捷导航里面打开的,现在我一点快捷导航发现我的收 ...

额, 这个不清楚,请下站长@mofangbao
密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2014-6-16 10:45:38 | 显示全部楼层
给力,一直觉得这个很麻烦
密码修改失败请联系微信:mofangbao
您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

Copyright ©2011-2014 bbs.06climate.com All Rights Reserved.  Powered by Discuz! (京ICP-10201084)

本站信息均由会员发表,不代表气象家园立场,禁止在本站发表与国家法律相抵触言论

快速回复 返回顶部 返回列表