在Fortran 90中,将行数组写入文本文件的好方法是什么?

And*_*rew 12 arrays fortran text-files

我是Fortran的新手,我希望能够以行方式(列之间的空格和自己行上的每一行)将二维数组写入文本文件.我尝试了以下内容,它似乎在以下简单示例中工作:

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace")
  DO i=1,numrows
    WRITE(12,*) (a(i,j), j=1,numcols)
  END DO
END PROGRAM test3
Run Code Online (Sandbox Code Playgroud)

正如我所说,这似乎在这个简单的例子中工作正常:生成的文本文件aoutput.txt包含第1行的数字1-762,第2行的数字763-1524,依此类推.

但是,当我在一个更复杂的程序中使用上述想法(即,上面的倒数第五个,倒数第四个,倒数第三个,倒数第二个以上的代码行)时,我运行陷入麻烦; 看来,每一行都是间歇性地(通过一条新线)划界.(我没有发布,也可能不会发布,这里是我的整个复杂的程序/脚本 - 因为它很长.)在我复杂的程序/脚本中缺少一致的行分隔符可能表明我的代码中存在另一个错误,而不是上面的四行写入文件例程,因为上面的简单示例似乎工作正常.不过,我想知道,你能不能帮我想一想,如果有一个更好的行写文本文件例程,我应该使用它?

非常感谢您的宝贵时间.对此,我真的非常感激.

Jon*_*rsi 16

这里有一些问题.

根本的一点是,您不应该将文本用作大量数据的数据格式.它很大而且很慢.文本输出适合您自己阅读的内容; 你不会坐下来打印出381万个整数并翻阅它们.如下面的代码所示,正确的文本输出比二进制输出慢大约10倍,并且大50%.如果移动到浮点值,则使用ascii字符串作为数据交换格式会出现精度损失问题.等等

如果您的目标是与matlab交换数据,那么将数据写入matlab可以读取的格式相当容易; 您可以使用matlab中的matOpen/matPutVariable API,或者将其写为matlab可以读取的HDF5数组.或者您可以在原始Fortran二进制文件中写出数组,如下所示,并让matlab读取它.

如果你必须使用ascii写出巨大的数组(如上所述,这是一个糟糕而缓慢的想法),那么你在列表drected IO中遇到了默认记录长度的问题.最好是在运行时生成一个格式字符串,它正确地描述了你的输出,对于这么大(~5000个字符宽!)的行来说最安全的是将记录长度明确地设置为大于你要打印出来的值.因此fortran IO库无法帮助您分解行.

在下面的代码中,

  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
Run Code Online (Sandbox Code Playgroud)

生成字符串rowfmt在这种情况下会(762(1X,I6))是您将用于打印输出的格式和RECL选项OPEN设置的记录长度是东西比7张*数numCols + 1大.

PROGRAM test3
  IMPLICIT NONE

  INTEGER :: i, j, k, numrows, numcols
  INTEGER, DIMENSION(:,:), ALLOCATABLE :: a
  CHARACTER(LEN=30) :: rowfmt
  INTEGER :: txtclock, binclock
  REAL    :: txttime, bintime

  numrows=5001
  numcols=762
  ALLOCATE(a(numrows,numcols))
  k=1
  DO i=1,SIZE(a,1)
    DO j=1,SIZE(a,2)
      a(i,j)=k
      k=k+1
    END DO
  END DO

  CALL tick(txtclock)
  WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
  OPEN(UNIT=12, FILE="aoutput.txt", ACTION="write", STATUS="replace", &
       RECL=(7*numcols+10))
  DO i=1,numrows
    WRITE(12,FMT=rowfmt) (a(i,j), j=1,numcols)
  END DO
  CLOSE(UNIT=12)
  txttime = tock(txtclock)

  CALL tick(binclock)
  OPEN(UNIT=13, FILE="boutput.dat", ACTION="write", STATUS="replace", &
       FORM="unformatted")
  WRITE(13) a
  CLOSE(UNIT=13)
  bintime = tock(binclock)

  PRINT *, 'ASCII  time = ', txttime
  PRINT *, 'Binary time = ', bintime

CONTAINS

    SUBROUTINE tick(t)
        INTEGER, INTENT(OUT) :: t

        CALL system_clock(t)
    END SUBROUTINE tick

    ! returns time in seconds from now to time described by t
    REAL FUNCTION tock(t)
        INTEGER, INTENT(IN) :: t
        INTEGER :: now, clock_rate

        call system_clock(now,clock_rate)

        tock = real(now - t)/real(clock_rate)
    END FUNCTION tock
END PROGRAM test3
Run Code Online (Sandbox Code Playgroud)


can*_*nin 10

这可能是一种非常迂回且耗时的方式,但无论如何......你可以简单地打印每个数组元素,advance='no'在你的write语句中使用(以抑制在打印内容后插入换行符).完成一行后,使用"普通" write语句获取换行符,然后在下一行重新开始.这是一个小例子:

program testing

implicit none

integer :: i, j, k

k = 1

do i=1,4
   do j=1,10
      write(*, '(I2,X)', advance='no') k
      k = k + 1
   end do
   write(*, *) ''  ! this gives you the line break
end do

end program testing
Run Code Online (Sandbox Code Playgroud)

运行此程序时,输出如下:

 1  2  3  4  5  6  7  8  9 10  
11 12 13 14 15 16 17 18 19 20  
21 22 23 24 25 26 27 28 29 30  
31 32 33 34 35 36 37 38 39 40
Run Code Online (Sandbox Code Playgroud)