プログラミング応用b 第5回 『ファイル入出力の基礎1』-05 バイトデータ入出力系ストリームの使用例


 【バイトデータ入出力の使用例】
 それでは,いよいよ実際にバイト入出力系ストリームを使用してみよう。なお,ストリームの
機能を使用するにあたって注意しなければならない点として,例外の扱いがある。基本的に,入
出力系のメソッドのほとんどは,なにか処理に支障が出るとIOExceptionおよびそのサブクラス型
の例外を投る。Table 1〜6に,どのような例外が各メソッドから投げられるかを示してあるので,
よく確認しておくこと。

●FileInputStream/FileOutputStrem/BufferedInputStream/BufferedOutputStream の使用例
 List 1は, filestreamtest1.data という名前のファイルにバイトデータを書き込み,続けてその
ファイルからバイトデータを読み込むプログラムである。なお,filestreamtest1.dataファイルは,
Eclipseを使っている場合,プロジェクトフォルダ直下に生成される

注:実行時に既に filestreamtest1.data という名前のファイルがプロジェクトフォルダ内にあると
  内容を上書きするので,もし同名の重要なファイルがある場合には,プログラム内の filestreamtest1.data
  を別の名前に変えて実行するように。

List 1 (StreamTest1.java)

 さて,List 1だが,List 1-① で,java.ioパッケージをインポートしている。そして,List 1-②
から指定されたファイルに byte型データを書き込むメソッド writeBytesToFile( )が定義されている。
例外指定で IOException を投げることが明示されていることに注意。

 List 1-③ では,ファイルストリームとそれをラップするバッファストリームの変数が null初期化
をともなって宣言されている。そして, tryブロックの中で入出力処理が行われることになる。List 1-④
では,引数 name で指定されたファイルをオープンし,List 1-⑤では,それをラップする BufferedOutputStream型
オブジェクト bos が生成されている。bos のオープンが成功したら,バイトデータを write( )メソッド
で書き込んで,基本的な入出力処理が終わる。

 もしこの過程で例外が発生すれば,List 1-⑦ で例外をキャッチして,適切な処置を行う。最後に,
finallyブロックで,例外が発生したかどうかにかかわらず,ストリームをクローズする(List 1-⑧,⑨)。
このクローズ時に例外が発生すると,例外はこのメソッドの外に伝搬していく(そのために,List 1-②
で例外指定をしてある)。

 ここで,重要なことが2点あげられる。まず,List 1-③ でtryブロックの前でストリーム変数を null
に初期化し, finallyブロックでクローズするときに,このストリーム変数が nullでないか確かめてから
クローズしていることである(List 1-⑧,⑨)。もし,List 1-④,⑤ のいずれかで例外が発生す
ると,ストリーム変数 fos,もしくは fos と bos の両方が null のままになるので,List 1-⑧,⑨ の
ように,null でないか確かめてから close( ) メソッドを呼ばないといけないのである。例えば,fosの
値が null のまま fos.close( ); としてしまうと,NullPointerException 例外が発生してしまう(null値は
何のオブジェクトも参照していないという特別な参照値であることを思い出そう)。
 そして,List 1-⑧,⑨ ではラッパストリーム→元のストリームの順でクローズしていることが重要
である。Fig.3を見ればわかるが,元のストリーム自身は,ラッパストリームから一方的に参照されるだけ
で,自分がラッパストリームにラップされていることを知らない。そのため,元のストリームをラッパより先
にクローズすると,ラッパストリームが管理しているバッファがフラッシュされないままになってしまう

よって,まずラッパストリームを先にクローズしなくてはならないのである。

 前述したように,ラッパストリームをクローズすれば,通常は自動的に元のストリームもクローズされ
る。そうすると,正常に入出力が行われた場合は,List 1-⑧,⑨ で元のストリームは2回クローズさ
れることになる。しかし,心配は無用である。すでにクローズされたストリームを再度クローズしてもエ
ラーにならない
のである。そのため,List 1-④ で例外が発生してラッパストリームだけが生成されなか
った場合のことも考えて,List 1-⑨ で元のストリームをクローズしておくべきなのである。

 List 1-⑩ から始まっている読み込み用メソッド readBytesFromFile( ) の定義でも同様の注意が
必要である。List 1-⑪〜⑰ の一連の流れをList 1-③〜⑨ と比べてみて欲しい。このように,List 1
の部分(List 1-③〜⑨ と List 1-⑪〜⑰)をまとめると,下図の様なストリームを使った入出力手順
の定番パターンが得られる。ストリームを使った入出力を行うときは,この手順に従って処理を書けば
良い。


 なお,List 1-⑭では, read( )メソッドで読み込んだデータがint型で返されることに注意しよう。
そのため,read( )メソッドで読み込んだ値をいったん int型の変数bに格納し,byte型配列に格納
する際に, dist[ i ] = (byte) b として,bの値をint型からbyte型に変換してから代入している
(キャスト演算子による型変換を思いだそう)。

  さて, main( )メソッドでは, List 1-⑱ で元データを格納する配列と,データを読み込む配列を宣言
している。List 1-⑲,⑳ で,データの書き込みと読み込みをしているわけである。最後に,クローズ時に
例外が発生したときの対処として,List 1-㉑ に chatchブロックが用意されている。


●DataInputStream/DataOutputStream の使用例
次に,基本的なデータ型の値をバイトデータとして入出力する
  ・DataInputStream
  ・DataOutputStream
の使用例を見てみよう。
 List 2 は,指定されたファイルにint型データと double型データをバイトデータとして書き
込んだ後,逆にファイルからデータを読み込む例である。List 2では, datastreamtest1.data
という名前のファイルに書き込むが, datastreamtest1.dataファイルは, clipseを使っている場合,
プロジェクトフォルダ直下に生成される
。同名の重要なファイルがすでにプロジェクトフォルダ内に
ある場合は,List 2で読み書きするファイル名を,上書きしてもかまわない名前に変更するように。
List 2 DataStreams.java

 まず DataOutputStream を使った書き込み用メソッドから説明していこう。DataOutputStream
はラッパストリームだが,List 1の例ではまず FileOutputStream ストリームを生成し,それ
を BufferedOutputStream でラップし,更に DataOutputStream でラップしている(List 1-①,②)。

 そして,オープンした DataOutputStream型ストリーム dos に対して, int型データと double
型データを書き込んでいるのが,List 1-③ の
  dos.writeInt( i );
  dos.writeDouble( d );
という部分である。DataOutputStream には,前回紹介したように, xxxxを基本データ型名と
すれば,
  writeXxxx( )
というメソッドがあり,これで xxxx型データをストリームに書き出すことができる。List 1-④
では,データをすべて書き込んだ後で,
  dos.flush();
として,念のためストリームをフラッシュしていることに注意。なお,ラッパストリームを使っ
ている場合,フラッシュを行うには,一番外側でラップしているラッパストリームの flush()メソ
ッドを呼ぶようにせよ


 例外については,前回解説したように IOException型例外が投げられる可能性があるので, catch
ブロックでキャッチするようになっている(List 1-⑤)。
 また,例外が発生したときも発生しないときも,ストリームを明示的にクローズするために
finallyブロックで close( ) メソッドを呼んでクローズしている。このとき,前回説明したように,
ラッパストリームからクローズすること。List 1のようにストリームが多重にラップされていて
も,基本は同じで,より“外側からラップしている”ストリームから順にクローズするようにす
る。つまり, List 1-⑥にあるように,まず DataOutputStream型ストリーム dos からクローズ
し,次に dos にラップされていた BufferedOutputStream型ストリーム bof をクローズ,最後に
bof にラップされていた基本ストリームである FileOutputStream型のストリーム fos をクローズ
することになる。

 DataInpuStream によるデータの読み込みでも,だいたい要点は同じである。まず,ストリー
ムのオープンだが,List 1の例では,書き込みの場合と同じように,ファイルストリームをオー
プンし,ついでバッファつきストリームでラップして,それをさらに DataInputStream でラップ
している(List 1-⑦,⑧)。
 データの読み込みを実際に行っているのは,List 1-⑨ の
  i = dis.readInt();
  d = dis.readDouble();
という部分である。前回紹介したように, DataInputStreamには,xxxxを基本データ型名とすれ
ば,readXxxx( ) というメソッドがあり,これによってデータ値の読み込みを行うわけである。実
際に読み込んだデータを表示しているのが List 1-⑩ の部分である。

 読み込み時の例外の扱いも,書き込みの場合と同じで,必要が有れば catchブロックで IOException
をキャッチし(List 1-⑪),finallyブロックで,ラッパストリームからクローズする(List 1-⑫)。

 実際に, main( )メソッドの中で,ファイルへデータを書き込み,書き込んだデータを読み込ん
でいる部分が,List 1-⑬である。List 1を実行すると,

  i is 123, d is 123.456

と表示され,ちゃんとデータの読み書きが行われたことが確認できる。



戻る