[英]In Fortran 90, what is a good way to write an array to a text file, row-wise?
我是 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
正如我所說,這在這個簡單的示例中似乎工作正常:生成的文本文件aoutput.txt包含第 1 行的數字 1-762,第 2 行的數字 763-1524,依此類推。
但是,當我在一個更復雜的程序中使用上述想法(即,上面倒數第五行、倒數第四行、倒數第三行和倒數第二行代碼)時,我運行陷入麻煩; 每行似乎只是間歇性地(由新行)分隔。 (我沒有發布,也可能不會在這里發布我的整個復雜程序/腳本——因為它相當長。)在我的復雜程序/腳本中缺乏一致的行分隔符可能表明我的代碼中存在另一個錯誤,而不是上面的四行寫入文件例程,因為上面的簡單示例似乎可以正常工作。 不過,我想知道,你能幫我想想我是否應該使用更好的逐行寫入文本文件例程?
非常感謝您的寶貴時間。 對此,我真的非常感激。
這里有幾個問題。
最基本的一點是,您不應該將文本用作大量數據的數據格式。 它很大而且很慢。 文本 output 適合您自己閱讀的內容; 您不會坐下來打印 381 萬個整數並翻閱它們。 如以下代碼所示,正確的文本 output 比二進制 output 慢約 10 倍,大 50%。 如果您使用浮點值,使用 ascii 字符串作為數據交換格式會出現精度損失問題。 等等
如果您的目標是與 matlab 交換數據,則將數據寫入 matlab 可以讀取的格式相當容易; 您可以使用 matlab 中的 matOpen/matPutVariable API,或者將其寫為 matlab 可以讀取的 HDF5 數組。 或者你可以在原始 Fortran 二進制文件中寫出數組,如下所示,並讓matlab 讀取它。
如果您必須使用 ascii 來寫出巨大的 arrays(如前所述,這是一個糟糕且緩慢的想法),那么您將在列表導向的 IO 中遇到默認記錄長度問題。 最好的方法是在運行時生成一個格式字符串,它可以正確描述您的 output,最安全的是對於如此大的(約 5000 個字符寬)行。將記錄長度顯式設置為大於您將要打印的長度以便 fortran IO 庫不會幫助您打破界限。
在下面的代碼中,
WRITE(rowfmt,'(A,I4,A)') '(',numcols,'(1X,I6))'
生成字符串 rowfmt ,在這種情況下為(762(1X,I6))
,這是您將用於打印的格式,並且OPEN
的RECL
選項將記錄長度設置為大於 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
這可能是一種非常迂回且耗時的方式,但無論如何......您可以簡單地單獨打印每個數組元素,使用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
當你運行這個程序時,output 如下:
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
使用“*”是針對列表的 IO -- Fortran 將為您做出決定。 未指定某些行為。 您可以使用格式語句獲得更多控制權。 如果您想明確識別行邊界,請在每行后寫一個標記符號。 就像是:
DO i=1,numrows
WRITE(12,*) a(i,:)
write (12, '("X")' )
END DO
幾個小時后的附錄:
也許對於您用來檢查文件的某些程序來說,對於較大的 numcols 值,這些行太長了? 對於 output 語句,請嘗試:
WRITE(12, '( 10(2X, I11) )' ) a(i,:)
如果矩陣的列超過 10 列,這會將矩陣的每一行分解為文件中的多個較短的行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.