2

我正在尝试使用 python 读取一个非常大的 Fortran 无格式二进制文件。该文件包含 2^30 个整数。

我发现记录标记令人困惑(第一个是-2147483639),无论如何我已经实现了恢复数据结构(那些想要的整数都是相似的,因此与记录标记不同)并编写下面的代码(借助here)。

但是,我们可以看到每条记录的开头和结尾的标记并不相同。这是为什么?

是因为数据的大小太长( 536870910 = (2^30 - 4) / 2 )?但是 (2^31 - 1) / 4 = 536870911 > 536870910。

还是只是数据文件的作者犯了一些错误?

另一个问题,记录开头的标记类型是什么,int 还是 unsigned int?

fp = open(file_path, "rb")

rec_len1, = struct.unpack( '>i', fp.read(4) )
data1 = np.fromfile( fp, '>i', 536870910)
rec_end1, = struct.unpack( '>i', fp.read(4) )

rec_len2, = struct.unpack( '>i', fp.read(4) )
data2 = np.fromfile( fp, '>i', 536870910)
rec_end2, = struct.unpack( '>i', fp.read(4) )

rec_len3, = struct.unpack( '>i', fp.read(4) )
data3 = np.fromfile( fp, '>i', 4)
rec_end3, = struct.unpack( '>i', fp.read(4) )
data = np.concatenate([data1, data2, data3])

(rec_len1,rec_end1,rec_len2,rec_end2,rec_len3,rec_end3)

这是读取的记录长度值,如上所示:

(-2147483639, -2176, 2406, 589824, 1227787, -18)
4

3 回答 3

2

最后,事情似乎更清楚了。

是英特尔 Fortran 编译器用户和参考指南,请参阅记录类型:可变长度记录部分。

对于大于 2,147,483,639 字节的记录长度,该记录分为子记录。子记录的长度可以是 1 到 2,147,483,639,包括 1 到 2,147,483,639。

前导长度字段的符号位指示记录是否继续。尾随长度字段的符号位表示存在前面的子记录。符号位的位置由文件的字节序格式决定。

连续的子记录具有符号位值为 1 的前导长度字段。组成记录的最后一个子记录具有符号位值为 0 的前导长度字段。具有前面子记录的子记录具有尾随长度符号位值为 1 的字段。组成记录的第一个子记录具有符号位值为 0 的尾随长度字段。如果符号位的值为 1,则记录的长度存储在两个-补符号

写了很多文章后,我意识到我被二进制补码符号误导了,记录标记只是根据上面的规则改变符号,而不是在符号位为 1 时更改为二进制补码符号。无论如何,我的也有可能数据是用不同的编译器创建的。

下面是解决方案。

数据大于2GB,因此分为几个子记录。正如我们看到的第一个记录开始标记是 -2147483639,所以第一个记录的长度是 2147483639,这正是子记录的最大长度,而不是我认为的 2147483640,也不是 2147483638 的二进制补码符号-2147483639。

如果我们跳过 2147483639 字节来读取记录结束标记,您将得到 2147483639,因为它是结束标记为正的第一个子记录。

以下是检查记录标记的代码:

fp = open(file_path, "rb")
while 1:
    prefix, = struct.unpack( '>i', fp.read(4) )
    fp.seek(abs(prefix), 1)    #or read |prefix| bytes data as you want
    suffix, = struct.unpack( '>i', fp.read(4) )
    print prefix, suffix
    if abs(suffix) - abs(prefix): 
        print "suffix != prefix!"
        break
    if prefix > 0: break

和丝网印刷

-2147483639 2147483639
-2147483639 -2147483639
18 -18

我们可以看到记录开始标记和结束标记除了符号之外总是相同的。三个记录的长度分别是2147483639、2147483639、18个字节,不一定是4的倍数。所以第一个记录以某个整数的前3个字节结束,第二个记录从剩下的1个字节开始。

于 2013-04-24T13:31:37.077 回答
0

由于这个问题似乎经常出现.. 这是一个 python 实用程序代码,用于扫描二进制文件并确定它是否(可能是)fortran 未格式化的顺序访问文件。它通过尝试几种标题格式来工作。当然,由于“未格式化”格式不是标准的,可能还有其他变体,但这应该是最常见的。

请注意左括号已转义,因此您可能需要更改 & #060; 如果您屏幕复制此内容,则返回“小于”符号。


def scanfbinary(hformat,file,fsize):
 """ scan a file to see if it has the simple structure typical of
     an unformatted sequential access fortran binary:
     recl1,<data of length recl1 bytes>,recl1,recl2,<data of length recl2 bytes>,recl2 ...
 """
 import struct
 print 'scan type',hformat,
 if 'qQ'.find(hformat[1])>=0:  hsize=8
 elif 'iIlL'.find(hformat[1])>=0:  hsize=4
 if hformat[0] == '<':  endian='little'
 elif hformat[0] == '>':  endian='big'
 print '(',endian,'endian',hsize,'byte header)',
 f.seek(0)
 nrec = 0
 while fsize > 0:
  h0=struct.unpack(hformat,f.read(hsize))[0]
  if h0 < 0 :   print 'invalid integer ',h0; return 1
  if h0 > fsize - 2*hsize:
   print 'invalid header size ',h0,' exceeds file size ',fsize
   if nrec > 0:print 'odd perhaps a corrupe file?'
   return 2
# to read the data replace the next line with code to read h0 bytes..
# eg 
#  import numpy
#  dtype = numpy.dtype('<i')
#  record=numpy.fromfile(f,dtype,h0/dtype.itemsize) 
  f.seek(h0,1)   
  h=struct.unpack(hformat,f.read(hsize))[0]
  if h0!=h :  print 'unmatched header';   return 3
  nrec+=1
  if nrec == 1:print
  if nrec < 10:print 'read record',nrec,'size',h
  fsize-=(h+2*hsize)
 print 'successfully read ',nrec,' records with unformatted fortran header type',hformat
 return 0
f=open('binaryfilename','r')
f.seek(0,2)
fsize=f.tell()
res=[scanfbinary(hformat,f,fsize) for hformat in ('<q','>q','<i','>i')]
if res.count(0)==0:
 print 'no match found, file size ',fsize, 'starts..'
 f.seek(0)
 for i in range(0,12): print f.read(2).encode('hex_codec'),
 print 

于 2013-03-27T21:08:44.743 回答
0

我发现使用 f2py 是 python 访问 fortran 数据的一种更方便的方式。然而,记录标记的奇怪行为仍然是一个问题。至少我们可以避免深入(有时令人困惑)fortran 无格式文件结构。它与 numpy 很好地匹配。

F2PY 用户指南和参考手册在这里。这是一个用于打开和关闭文件、读取整数一维数组和浮点二维数组的示例 fortran 源文件。注意注释以 !f2py 开头,它们有助于使 f2py 更“聪明”。

要使用它,您需要将其包装到一个模块中并导入到 python 会话中。然后你可以像那些 python 函数一样调用这些函数。

!ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
!cc                                                         cc
!cc      FORTRAN MODULE for PYTHON PROGRAM CALLING          cc
!cc                                                         cc
!ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc

!Usage: 
!   Compile:   f2py -c fortio.f90 -m fortio
!   Import:    from fortio import *
!       or     import fortio
!Note:
!   Big endian: 1; Little endian: 0


!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE open_fortran_file(fileUnit, fileName, endian, error)
  implicit none

  character(len=256) :: fileName
  integer*4 :: fileUnit, error, endian
  !f2py integer*4 optional, intent(in) :: endian=1
  !f2py integer*4 intent(out) :: error

  if(endian .NE. 0) then
     open(unit=fileUnit, FILE=fileName, form='unformatted', status='old', &
          iostat=error, convert='big_endian')
  else
     open(unit=fileUnit, FILE=fileName, form='unformatted', status='old', &
          iostat=error)
  endif
END SUBROUTINE 

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE read_fortran_integer4(fileUnit, arr, leng)
  implicit none

  integer*4 :: fileUnit, leng
  integer*4 :: arr(leng)
  !f2py integer*4 intent(in) :: fileUnit, leng 
  !f2py integer*4 intent(out), dimension(leng), depend(leng) :: arr(leng)

  read(fileUnit) arr
END SUBROUTINE

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE read_fortran_real4(fileUnit, arr, row, col)
  implicit none

  integer*4 :: fileUnit, row, col
  real*4 :: arr(row,col)
  !f2py integer*4 intent(in):: fileUnit, row, col
  !f2py real*4 intent(out), dimension(row, col), depend(row, col) :: arr(row,col)

  read(fileUnit) arr
END SUBROUTINE

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE close_fortran_file(fileUnit, error)
  implicit none

  integer*4 :: fileUnit, error
  !f2py integer*4 intent(in) :: fileUnit
  !f2py integer*4 intent(out) :: error

  close(fileUnit, iostat=error)
END SUBROUTINE 
于 2013-03-25T07:08:08.563 回答