22 ストリーム入出力の使い方
ストリーム入出力はFortran 2003で導入された機能で、データを連続的なバイトストリームとして読み書きすることを可能とします。 また、POS=指定子を利用すると、特定の位置への書き出しや、特定位置からの読み込みが可能となります。
ストリーム入出力にはテキスト入出力用の「書式付きストリーム」とバイナリ入出力用の「書式なしストリーム」の2種類があります。
22.1 書式付きストリーム
書式付きストリームは従来からある書式付き順番探査(sequential)とほぼ同じように利用できるものですが、一部のレコードベース特有の制限がなく、より柔軟性の高いものとなっています。
書式付きストリームファイルをオープンする際に基本的な書式は以下の通りです:
open (装置番号, file='ファイル名', access='stream', form='formatted', ...)以下に書式付きストリームを用いてファイルへ書き出しを行うプログラム例を示します。
[ formatted_stream_io.f90 ] - 書式付きストリームを利用するシンプルなプログラム例
program formatted_stream_io implicit none integer :: out open (newunit=out, file='test.txt', access='stream', form='formatted', status='replace') write (out, '(A)') 'Fortran' write (out, '(i4)') 2003 close (out) end program formatted_stream_io
出力例) Fortran 2003このコード例が示す通り、書式付きストリームは書式付き順番探査の場合とほぼ同じように利用できます。 試しにこのプログラムの5行目のaccess='stream'となっている部分をaccess='sequential'と書き変えて書式付き順番探査ファイルとしてオープンするようにしても、このプログラムの動作は変わりません。
しかし例えば次のような長い行を出力するような場合、書式付き順番探査を用いると、処理系依存のレコード長の制限を受ける場合があり、 コンパイラによってはバッファーオーバーフローが発生する可能性があります。
write (w_unit, '(A)') repeat('x', 100000) ! buffer overflow might occur beyond recl書式付きストリームを用いれば、このようなレコード長の制限がありませんので、どの処理系でも同じよう動作し、このような問題は発生しません。
また、ストリーム入出力では最初に述べた通りpos=指定子を利用して、特定の位置への書き出しや、特定位置からの読み込みが可能です。 ここで、書式付きストリームの場合にpos=で指定できる位置は、ファイルの先頭を示す1か、inquire文を使用して事前に取得した位置に限定されています。 (書式なしストリームの場合はこのような制限はありません)
書式付きストリームの場合、pos=指定子で特定の位置を指定して、そこから書き込みを行った場合、それ以降の部分はすべて切捨てられますのでご注意ください。
以下にpos指定子を用いるコード例を示します。
[ pos_formatted_io.f90 ] - 書式付きストリームでpos指定子を用いる例
program pos_formatted_io implicit none integer :: w_unit, r_unit, pos_year, pos_last, year open (newunit=w_unit, file='test.txt', access='stream', form='formatted', status='replace') write (w_unit, '(A)') 'Fortran' inquire (w_unit, pos=pos_year) write (w_unit, '(i4)') 2003 inquire (w_unit, pos=pos_last) close (w_unit) print *, pos_last - 1, 'bytes written (including the EOLN char(s)).' open (newunit=r_unit, file='test.txt', form='formatted', access='stream', status='old') read (r_unit, '(i4)', pos=pos_year) year close (r_unit) print *, 'Year read is:', year end program pos_formatted_io
出力例) 15 bytes written (including the EOLN char(s)). Year read is: 2003このコードは書式付きストリームファイルとしてtest.txtをオープンし、そこにFortranという文字列と2003という数値を書き出しています。 その際に、2003という数値を書き出す直前にinquire文で現在位置をpos_yearに格納するようにしています。 その後、2003という数値を書き出した直後にもinquire文で現在位置をpos_lastに格納するようにし、ここで一旦ファイルをクローズしています。 ここで、pos_lastは、そこまでに出力された改行コードなども含んだバイト数+1がとなっていますので、 その後のプリント文で何バイト書き出されたのかを出力するようにしています。
更にその後、再度ファイルをオープンし、pos=指定子に先ほど取得したpos_yearの値を与えて、年の情報のみを読み込んで出力しています。
22.2 書式なしストリーム
ここでは書式なしストリームについて説明します。書式なしストリームを用いると、データをバイナリ形式で入出力できます。
書式なしストリームファイルをオープンする際に基本的な書式は以下の通りです:
open (装置番号, file='ファイル名', access='stream', form='unformatted', ...)書式なしストリームは書式なし順番探査と比較して主に以下のようなメリットがあります。
- ファイルサイズが小さくて済む。入出力オーバーヘッドが軽減されます。
- 任意のデータ構造が取り扱える。他の言語やアプリケーションとのデータのやり取りが容易になります。
- POS=指定子を利用して、ファイル内の任意の位置から読み書きができます。(書式付きストリームの場合よりも柔軟性が高い)
[ unformatted_stream_io.f90 ] - 書式なしストリーム用いるシンプルなコード例
program unformatted_stream_io implicit none integer :: year, month, out year = 2003 month = 11 open (newunit=out, file='test.dat', access='stream', form='unformatted', status='replace') write (out) year, month write (out) 'Fortran' close (out) end program unformatted_stream_ioこのプログラムを実行すると、以下の出力が得られます。
書き出されるバイト列:access='stream'の場合 (15 bytes) D3 07 00 00 0B 00 00 00 46 6F 72 74 72 61 6E整数は4バイトで表されていて、最初の4バイトがYearの値である2003を表し、次の4バイトがMonthの値である11を表しています。 そして、そのすぐ後ろにフォートランという文字列、7バイトが出力され、合計で15バイトが出力されています。
このように書式なしストリームの出力はデータ値のみで構成されます。
ここで試しに、このプログラムの7行目のaccess='stream'となっている部分をaccess='sequential'とし、書式なし順番探査を用いるように変更して再度実行すると、以下のように少し長い出力が得られます。
書き出されるバイト列:access='sequential'の場合 (31 bytes) 08 00 00 00 D3 07 00 00 0B 00 00 00 08 00 00 00 07 00 00 00 46 6F 72 74 72 61 6E 07 00 00 00先ほどとは異なり、今回の出力は各レコードの前後にレコード長情報が付加されていて、ファイルサイズも大きくなっています。
書式なし順番探査の出力はこのようにデータ値以外のレコード長情報が含まれますが、その形式は処理系依存となっています。 そのため、書式なし順番探査のデータは、異なるコンパイラ間で互換性が保証されていませんのでご注意下さい。
最初にも述べましたが、pos=指定子を利用すると、ファイル内の任意の位置から読み書きが可能となります。 ここで、書式なしストリームの場合にpos=で指定できる値は、書式付きストリームの場合のように、1かINQURIE文で取得された値のみに制限されてはいません。 また、pos=で位置を指定して書き込んだ場合も、書式付きストリームの場合とは異なり、それ以降のデータが切り捨てられてしまうということもありません。
以下のプログラム例では上述のプログラム(unformatted_stream_io)で書き出したファイルの、monthの情報のみを11から5に書き換えます。 (その他の部分が変更されたり消えてしまうことはありません)
[ pos_unformatted.f90 ] - 書式なしストリームでpos=用いるコード例
program pos_unformatted implicit none integer :: month, out month = 5 open (newunit=out, file='test.dat', access='stream', form='unformatted', status='old') write (out, pos=5) month ! update just the month at pos 5 close (out) end program pos_unformatted
実行前:(test.dat) D3 07 00 00 0B 00 00 00 46 6F 72 74 72 61 6E 実行後:(test.dat) D3 07 00 00 05 00 00 00 46 6F 72 74 72 61 6E
最後に書式なしストリームを利用して、ファイルの特定の位置のデータを読み込むプログラム例をお見せします。
以下のプログラム例はPNG形式の画像ファイルを開き、特定の位置に格納されている、画像の幅と高さを取得して出力します。
PNG画像の幅と高さはファイルの17バイト目と21バイト目に、ビッグエンディアンの4バイト整数としてそれぞれ格納されています。 このプログラムは、ビッグエンディアンの環境でも、リトルエンディアンの環境でも動くように設計されていて、整数を直接読み込むのではなく、一旦4バイト分のバイト配列(Integer(int8)bytes(4))に読み込み、それをプログラム内で数値に変換するようにしています。
[ read_png_dimensions.f90 ] - PNG画像の幅と高さ取得するコード例
program read_png_dimensions use iso_fortran_env, only: int32, int8 implicit none integer :: iunit integer (int32) :: width, height integer (int8) :: bytes(4) open (newunit=iunit, file='test.png', form='unformatted', access='stream', status='old') read (iunit, pos=17) bytes ! 17バイト目から幅をバイト配列に読み込む width = bytes_to_int32(bytes) ! バイト配列を数値に変換 read (iunit) bytes ! 21バイト目から高さをバイト配列に読み込む height = bytes_to_int32(bytes) ! バイト配列を数値に変換 print *, 'Width: ', width, ' Height: ', height close (iunit) contains ! バイト配列を数値に変換する関数 function bytes_to_int32(bytes) result (n) integer (int8), intent (in) :: bytes(:) integer (int32) :: n, unsigned_value, i n = 0 do i = 1, size(bytes) unsigned_value = bytes(i) if (unsigned_value<0) unsigned_value = unsigned_value + 256 n = ior(n, ishft(unsigned_value,(4-i)*8)) end do end function end program read_png_dimensions
出力例) Width: 280 Height: 110