プログラミング応用b 第7回『ファイル入出力の基礎』07-06. 『ランダムアクセスファイル』 |
■6.『ランダムアクセスファイル』
今回の冒頭で。入出力には大きく2種類あるということを紹介した。ひとつは,一連のデータ
がひとつひとつ順次に入出力されるタイプのもので,Javaではこの種の入出力を,ストリームと
いう概念で一括して扱っている(Fig.5)。
ストリームでは,データが順序よく入出力すればいいだけなので,多くの入出力機器をこ
のストリームの概念で扱うことができる。たとえば,キーボードやモニタ(出力端末)も
ストリームとして扱うことが可能である。
もうひとつのタイプは,メモリのように好きな位置のデータにアクセス(ランダムアクセ
ス)できる入出力特性を持っているものである。ランダムアクセスという強力なアクセスを
提供できる出力機器は少なく,事実上,ファイルだけと考えることができる。
Javaでは,ファイルに対するランダムアクセスによる読み書き機能を提供する RandomAccessFile
クラスが提供されている。ランダムアクセスファイルの概念を Fig.6 に示す。ランダムアク
セスファイルは,バイト単位でアクセス可能な配列と考えるとわかりやすいだろう(Fig.6-(1))。
ランダムアクセスファイルは,データをバイナリデータ(バイトデータ)として記録する。
Fig.6-(1) で図示してあるように,ランダムアクセスファイルでは,次に読み込みや書き込みを
行う位置を,ファイルポインタというもので管理している。Fig.6-(1) の例では,第4バイト目に
ファイルポインタがある(先頭のバイトが第0バイトから始まっていることに注意)。
このファイルポインタは,前後に好きなだけ自由に移動させることができるので,ランダムアクセス
が可能になっているのである。このファイルポインタを移動させる操作を,シーク(seek)と呼ぶ。
ファイルポインタの位置は,読み書き動作をすると自動的に進んで,次に読み書きする位置を指す
ようになる。Fig.6-(2) は, Fig.6-(1) の状態からint型データ(4バイト)を書き込んだ直後の様子
である。第4〜7バイトにデータが書き込まれた後,ファイルポインタは4バイト進んで第8バイトを
示すように設定され,次の読み書き動作のための備えを行う。
また,ランダムアクセスファイルが一般的なストリームと異なる点としては,書き込み可能なファイル
であれば,オープンしたファイルに対して,読み書きを同時に行うことが可能であることがあげられる。
ストリームの場合は,入力用と出力用に別々のストリームをオープンする必要があったが,ランダム
アクセスファイルはそんな必要は無いということである。
なお,ランダムアクセスファイルも大きい意味でストリームと呼ぶ場合もあるので注意すること。
■6.1. 『RandomAccessFileの詳細』
では, RandomAccessFile クラスの詳しい説明をしよう。Fig.7 に RandomAccessFile
のクラス階層を示す。RandomAccessFile は, DataInput インタフェイスと DataOutput
インタフェイスを実装しているだけである(図では省略しているが,もちろん Object を継承
している)。このように, RandomAccessFile は一般的なストリームのクラス階層からは切り
離されている。
インタフェイス DataInput は,プリミティブ型を中心としたデータ値を読み込むためのメソッドを集めた
もので,詳細を下表Table 3に示す。
インタフェイスDataOutputをプリミティブ型を中心としたデータ値をバイトデータとして書き込むためのメソッドを
集めたもので,その詳細を下表Table 6に示す。
Table 6 に, RandomAccessFile のコンストラクタとメソッドを示す。
まず,コンストラクタだが,ファイルを指定するほかに,オープンモードを指定する。
読み込み専用モードでファイルをオープンする場合は,
"r"
という文字列を第2引数に渡す。そして,読み込み・書き込み兼用モードでオープンする
場合は,
"rw"
を第2引数に指定する。読み込み・書き込み兼用モードでオープンしようとしたときに,
指定されたファイルが存在しない場合は,新しいファイルを作成しようとする。
間違ったモードの指定を行うと, IllegalArgumentException 例外を投げる。
IllegalArgumentException は, RuntimeException のサブクラスなので,いわゆる
“チェックされない例外”(「例外」学習回参照)である。要するに,「正しいオープンモー
ドを指定することで IllegalArgumentException の発生を防げ」ということである。
RandomAccessFile の独自メソッドとしては,ファイルをクローズする close( ), オー
プンしたファイルに結びつけられたチャンネルを返す getChannel( ),オープンした
ファイルのファイル記述子を返す getFD( ) がある。
シークに関しては,指定された位置にファイルポインタを移動する seek( ) や,現在の
ファイルポインタの位置を返す getFilePointer( ) がある。
また,ファイルの長さを返す length( ),ファイルの長さを強制的に変更する setLength( )
が用意されている。setLength( )で,より短くファイルを切りつめることもできる。この
とき,切り捨てられた部分にファイルポインタが位置していたら,ファイルポインタは
新しいファイルの長さ+1
の位置に設定される。ファイルをより長く拡張することもできる。このとき,拡張された
部分の値はどんな値になるかは不明である。このように,ファイルの長さを調節できる
のも,ランダムアクセスファイルの大きな特徴と言える。一般的なストリームでは長さを
仮定できないので,このような芸当は不可能である。 読み込むための独自メソッドとし
ては,バイト単位で読み込む3つのreadメソッドが用意されている。
その他,基本データ型を入出力するために DataInput と DataOutput から継承した
メソッド(第5回で習ったこちらとこちら)がある。ランダムアクセスファイルに対してプリ
ミティブ型のデータの読み書きを行う際は,これらの便利なメソッドを使うと良い。
■6.1.2. 『RandomAccessFile の使用例』
RandomAccessFile を実際に使用した例を,List 1 に示す。List 4で定義されているメソッド
UseRandomAccessFile( ) は,一連の double 型のデータをランダムアクセスファイルに1000
個保存し,100個おきのデータを読み出して,それを10倍して書き戻す,というものである。
RandomAccessFileTest1.data という名前のファイルが既に存在すると,上書きしようとする
ので,同じ名前の大事なファイルがあるときには,別の名前に変更してから実行するように注
意すること。
List 4 RandomAccessFileTest.java
まず, tryブロックの前で, RandomAccessFile 型の変数 raf を宣言しておく(List 4-<①>)。
tryブロックの中で name で指定された名前のファイルを,読み出し・書き込み兼用モード
でオープンする(List 4-<②>)。
List 4-<③>のforループで,double 型データを1000個書き込む。具体的には,0.0か
ら始まって,0.01ずつ増やしつつファイルに書き込んでいる。書き込みには, writeDouble( )
メソッドを使用している。
書き込んだ1000個のデータのうち,0個目,100個目,200個目,…,900個目,と
いうように,100個おきに10個だけ読み込んで,その値を表示しているのが List 4-<④>
の for ループである。ループ用の変数iは 0 から 999 まで1ずつ増やされてされていく。
100個おきに double 型データ(8バイト)を読み込むために
raf.seek( i * 8 );
として,読み込むべき位置にファイルポインタを移動させている。double 型データの読み込み
には, readDouble( ) メソッドを使っている。このList 4-<④>の for ループが実行されると,
Fig.4-(1)のように表示され,確かにファイルにデータが書き込まれており,そのうちの100個
おきのデータを読み込むことに成功していることがわかる。
次に,List 4-<⑤>の for ループで,100個おきのデータを読み込み,その値をそれぞれ10倍
したうえで同じ位置に書き戻している。まず,
raf.seek( i * 8 );
として,目的の位置にシークする。そして,
d = raf.readDouble();
で, double 型データを1個読み込んで変数dに格納する。この読み込んだデータを
d *= 10;
として10倍する。そしてここが肝心なのだが,ふたたび
raf.seek( i * 8 );
としてシークする。なぜなら,直前の readDouble( ) メソッドの呼び出しで,ファイルポ
インタが移動してしまうからである。ですから,ここで読み込んだデータの位置にファ
イルポインタを戻すわけである。最後に writeDouble( ) メソッドを
raf.writeDouble( d );
と呼び出して,10倍した値を元の位置に書き戻す。
List 4-<⑥>で,実際に100個おきのデータを読み込んで表示している。この部分が
実行されると, Fig.4-<②>のように表示され,実際にファイル上の1000個の double 型
データのうち,100個おきのデータが10倍されていることがわかる。
List 4-<⑦>は例外のキャッチ,List-<⑧>はファイルのクローズである。main( )メソッ
ドのtryブロック(List 4-<⑨>)の中でメソッド UseRandomAccessFile( ) を呼び出す。
投げられた例外は, List 4-<⑩>でキャッチするようにする。
List 4の例では,ランダムアクセスファイルを double型の配列のように扱った。しか
し,様々な型のデータを混合して書き込むことももちろん可能である。