Who am I

我的照片
Hefei, Anhui, China
Research Fields: Satellite Geodesy

2014年5月5日星期一

fortran 关于二进制顺序读取

一: 关于二进制的读取
1)--------------------------------------------------------------------------------------
FOR的输出分 有格式form = 'formatted'、无格式form = 'unformatted'两种,前者是默认输出格式,即如果open语句里不声明form的话,那就是formatted。
无格式又分 直接存取access = 'direct'、间接存取access = 'seuqential' 两种,后者是无格式文件的默认格式。几个open示例:有格式文件:
open( 1, file = '...', status = 'new', form = 'formatted' )无格式/顺序文件:
open( 1, file = '...', status = 'new', form = 'unformatted', access = 'sequential' )无格式/直接文件: open( 1, file = '...', status = 'new', form = 'unformatted', access = 'direct', recl = ... )

2)--------------------------------------------------------------------------------------
如果是向 有格式文件 输出,则write语句写成:write( 1,* ) var; 或如write( 1,'(5i2)' ) var
如果是向 无格式/顺序文件 输出,则write语句应写成: write( 1 ) var
如果是向 无格式/直接文件 输出,则write语句应写成: write( 1, rec = k ) var

3)--------------------------------------------------------------------------------------
对于 无格式/直接文件 , open中的recl指明了一个输出记录的长度。它具体等于多少要看输出write语句如何写,同时write语句中的记录号rec也要写正确。
比如有一个m(20,30)的二维数组,可以有如下几种写法(注意recl, rec, m的变化):
a) 把整个数组看作一个记录(1个记录,1个记录长度20*30):
open ( 1, file = '...', status = 'new', form = 'unformatted', access = 'direct', recl = 20*30 )
write( 1, rec = 1 ) m
b) 把一行看作一个记录(20个记录,1个记录长度30):
open ( 1, file = '...', status = 'new', form = 'unformatted', access = 'direct', recl = 30 )
do k = 1, 20 
    write( 1, rec = k ) ( m( k, p ), p = 1, 30 )
end do
c) 把一个数组元素看作一个记录(20*30个记录,1个记录长度1):
open ( 1, file = '...', status = 'new', form = 'unformatted', access = 'direct', recl = 1 )
k = 0
do i = 1, 20 
   do j = 1, 30 
         k = k + 1 
         write( 1, rec = k ) m( i, j ) !rec表示输出数据的位置
或者 
         write( 1, rec = (i-1)*30+j ) m( i, j ) 
   end do
end do
注:二维数组是先存列再存行的,所以上述a)打印m元素的顺序与c)不同,而与下述d)相同
d) 把一个数组元素看作一个记录:
open ( 1, file = '...', status = 'new', form = 'unformatted', access = 'direct', recl = 1 )
k = 0
do j = 1, 30 
    do i = 1, 20 
           k = k + 1 
           write( 1, rec = k ) m( i, j )
或者  
           write( 1, rec = (j-1)*20+i ) m( i, j ) 
    end do
end do

在一些Fortran编译器中,对recl解释成字长而非字节,因此还需要将recl乘以4才能正确读取。后面将会详细讲解gfortran和ifort在这点上的区别。

4)--------------------------------------------------------------------------------------
二进制存放的数据还有一个反位的问题。一般在UNIX机上是二进制数是高位结束,在WIN和LINUX中是低位结束(没记错的话)。如果读数时出现很大的怪数,比如1.e+35之类,或者执行时显示读取文件到末尾,或者是记录不够长,并且你又确定程序没错,那么多半是反位问题的原因。这时如果你用的编译器有反位选项,可以加上,比如ifort编译器可以写:
ifort -convert big_endian xxx.f90   
ifort -convert little_endian xxx.f90
也可以直接在FOR程序的OPEN语句中说明,比如:
open( 1, file='...', form='unformatted', access='direct', recl=..., convert='big_endian' ) 
open( 1, file='...', form='unformatted', access='direct', recl=..., convert='little_endian') big_endian 适用于在本地PC读取UNIX服务器上生成的二进制数据。little_endian适用于在UNIX服务器读取本地PC生成的二进制数据。

二: 不同编译器的差别

1. 程序举例
program test
implicit none
real(kind=4) :: sst(144,73),q(144,73)
integer :: i,j,recl1
recl1=144*73*4
sst(:,:)=10.0
q(:,:)=20
open(11,file='out.grd',form='unformatted',access='direct',status='replace',recl=recl1)
write(11,rec=1) ((sst(i,j),i=1,144),j=1,73)
write(11,rec=2) ((q(i,j),i=1,144),j=1,73)
close(11)
end program

2. 分析
kind=4表示单精度数据占用4个字节,一般默认也是4个字节; recl的设置,不同的编译器是不同的:
1> 在pgi和gfortran编译器下, recl1=144*73*4,生成的二进制文件大小是84096字节(84096=144*73*4*2)。
2> 在ifort编译器下,recl1=144*73*1, 生成的二进制文件大小同上。
因此说明不同编译器在读取(写入)二进制文件时有*4或不*4的问题,pgi、gfortran需要,而ifort不需要。

如果是双精度数据时,在pgi和gfortran编译器下,recl=144*73*8,而ifort编译器下recl=144*73*2

如果用顺序(sequential)的方式读取二进制文件,文件开始和结束会自动加上文件标志符,而不同的编译器处理这点是有差别的,所以,当涉及到多个编译器时,建议用direct的方式读取二进制数据,因为可控性强。

另外,如果一次输出或读取的数据很大时,可能会超过recl(默认是kind=4)的数据区间,这时需要将数据分段输出或读取

三:参考资源
http://bbs.lasg.ac.cn/bbs/thread-13889-1-9.html
http://bbs.06climate.com/forum.php?mod=viewthread&tid=14763