オブジェクト指向プログラミングb 第5回


●入出力処理(前編)「もっとJavaを学ぶ 第3回 (2003年11月 )」

 プログラムを学習しはじめても,なかなか自分で役に立つプログラムを作成できない
場合もあるのではないだろうか。しかし,ファイルからデータを読み込んだり,ファイル
にデータを書き込んだりすることによって,プログラムの応用範囲は飛躍的に広がる。
 今回から,Javaの入出力について解説する。

【入出力】
 まず,コンピュータで言う「入出力(input/output)」ということを考えてみよう。
プログラムが通常,扱う世界は,おおざっぱに言って,「CPUとメモリ」である。Java
で言えば,「Java仮想マシンの中」ということになる。
 しかし,「CPUとメモリ」の世界だけでプログラム処理を行っても意味が無い。たとえ
ば,計算を行ったら,その結果を外界に伝えなくてはならない。そのため,私たちは
  System.out.println((a * b) / 2.0);
というようなメソッド呼び出しを行って,計算結果を表示端末の画面に表示しているので
ある。これは,まさに「CPUとメモリ」の外部(この場合は,表示端末)へ情報を送り出し
て,人間に対して計算結果を伝えていることになる。このように,「CPUとメモリ」の外
部にデータを送り出すことを,「データを出力(output)する」と言う。
 また,「CPUとメモリ」の閉じた世界の中で,いつも同じデータに対して同じ処理を行
っても,同じ結果が得られるだけで,これもまた,あまり意味のないものと言える。ソフ
トウェアが有意義な「仕事」をするためには,必要に応じて,「CPUとメモリ」の世界の
外からデータを取り入れて,処理を行う必要がある。このように,外界から「CPUとメモ
リ」の世界へデータを取り入れることを,「データを入力(input)する」と言う。

【入出力方法】
 さて,肝心の入出力方法だが,主に2つのアプローチがある。

●ランダムアクセス入出力
 ひとつは,ファイルなどの入出力先をまるで配列(同じデータ型の多くのデータが隙間無
く詰められたもの)であるかのように扱って,ランダムアクセスを行う方法である。
 ランダムアクセスとは,アクセスしたいデータへ直にアクセスするアクセス方法である。
たとえば,配列では要素へのアクセスはランダムアクセス方式で行われる。aという名前の
配列があったとそして,aの10番目の要素(a[10])へアクセスしたいときは,その要素へ直
接アクセスできる。
 たとえばJavaには,ファイルを配列のようなものと考え,ファイルに記録されているデー
タにランダムアクセスすることを可能にするRandomAccessFileというクラスがある。
 しかし,ランダムアクセスを行うには,入出力先の外部機器自体がランダムアクセス機能
を提供していなくてはならない。そのような外部機器は少なく,実質的には,ファイルを記
録するディスクドライブしかないと言って良いだろう。

●ストリームによる入出力
 もうひとつの方法は,外部からデータを順々に入力したり,外部へデータを順々に出力し
ていくような方法である。Javaをはじめ,多くのプログラミング言語では,このような入出
力を,ストリーム(stream)という概念で表現している。
 下図Fig.1にストリームの概念を示す。ストリームは,外界と「CPUとメモリの世界」(Java
仮想マシン)を結ぶ「データの流れ道」のようなもので,このストリームの中をデータが順々
に流れていく。


 たとえば,上図Fig.1では,キーボードから入力された“hello!”という文字を表す6個の
バイトデータが,最初に'h',次に'e',という具合にストリームに流れていき,やはり'h','e',
'l','l','o','!'の順番でJava仮想マシンに入力されていく。同様に,“hello!”という文字
を表す6個のバイトデータが出力用のストリームを通って,モニタ(正確に言えば,表示端末)
に出力・表示される。streamとは,英語では「小川」を意味する単語である。まさにストリ
ームはデータの流れる小川と言える。
 ここで重要なのは,入力元からストリームへ入ったデータは,その順番どおりにJava仮想
マシンへ入力されることである。同様に,Java仮想マシンから出力されたデータは,その出
力された順番どおりに出力先へ届く。ここがストリームのアクセスがランダムアクセスと大
きく異なるところである。
 ストリームによって入出力する外部機器は,ランダムアクセスのような高機能を持つ機器
である必要は無い。入力元の機器は,データを順次ストリームに送り出す機能を持ってれば
いいだけであるし,出力先の機器は,ストリームからやってくるデータを順次受け入れる能
力を持っていればOKである。そのため,ほとんどの入出力機器をストリームで扱うことが可
能
である。そのためFig.1に示すように,ファイルはもちろん,キーボードや表示端末とのデ
ータのやりとり,そしてデータ通信でのデータの送受にもストリームを使うことができるの
である。

【ストリームのオープン・クローズ】

 ストリームは,ストリームオブジェクトと呼ばれる専用のオブジェクトによって管理され
る(Fig.1)。ストリームオブジェクトは,生成時にコンストラクタで指定された入出力の対象
をストリームに結びつけ,データ入出力の準備を行う。この作業を,ストリームのオープン
(open)
と言う
。オープンされたストリームに対しては,読み書きなどの操作が可能になる。
 入出力する必要が無くなったら,入出力先とストリームの関連づけを解除し,ストリームを
閉じる。この作業をストリームのクローズ(close)と呼ぶ。ストリームをクローズするには,
ストリームオブジェクトに用意されているclose()メソッドを使用する
。
 Javaのオブジェクトは参照されなくなった段階で,自動的にメモリ領域が解放されるよう
になっているが,そのときにストリームオブジェクトはストリームのクローズを自動的に
行う。しかし,入出力の必要が無くなったストリームは,close()メソッドを利用して,でき
るだけ早い段階で明示的にクローズするべき
である。たとえば,オペレーティングシステムに
よっては,同時に開くことのできるファイルの上限数が制限されている場合があり,ストリー
ムのクローズが遅れれば,他のプログラムでファイルが開けなくなったりすることが起こりえ
るからである。
 まとめると,ストリームの入出力の手順は,
  (1)ストリームのオープン
    (ストリームオブジェクトの生成)
  (2)ストリームを使った入出力
  (3)ストリームのクローズ
という3段階になる。

【4系統の入出力ストリーム】
 ではまず,ストリームを扱うために,ストリームオブジェクトのクラス群をざっとながめて
みよう。ストリーム関係のクラス群は,java.ioというパッケージにまとめられている。ioとは,
Input/Outputの略である。
 さて,Fig.1にあるように,ストリームによる入出力では,入力には入力用ストリームを,出
力用には出力用のストリームを使用する
。つまり,入力ストリーム用のクラスと,出力ストリ
ーム用のクラスがあることになる。
 また,ストリームに入出力するデータが,
 ・テキストを構成する文字データか
 ・画像や数値データのようなバイト単位のバイナリデータ(バイトデータと言う)か
という違いもある。バイトデータの入出力ではバイトごとにデータの入出力を行うが,「文字」
というものは,文字コードによって,同じ文字が1バイトで表現されていたり,2バイトで表現
されていたりするので,単純にバイト単位で入出力をするわけにはいかないのである。
 つまり,ストリームの種類は
  入出力の2系統 × バイト・文字の2系統
の計4系統のストリームに大きく分類できることになる。具体的には,
  (1) バイトデータ入力系統ストリーム
  (2) バイトデータ出力系統ストリーム
  (3) 文字データ入力系ストリーム
  (4) 文字データ出力系ストリーム
今回は,4系統のストリーム群のうち,
バイトデータ入出力系の2系統(入力と出力)のストリーム群について見てみよう。

【バイトデータ入力系統ストリーム】
 最初に,下図Fig.2のバイトデータ入力ストリーム系のクラス群を見てみよう。

●スーパークラスInputStream
 まず,バイトデータ入力ストリーム系のスーパークラスとして,InputStreamがある。
InputStreamは,バイトデータ用の入力ストリームが備えるべき基本的なメソッドを定義
している抽象クラスである。これらのメソッドを下表Table 1に示す。


 重要なメソッドとしては,開いたストリームをクローズするclose(),ストリームからバイト
データを読み込む3つのread()がある。その他,入力データを指定した数だけ読み飛ばすメソッ
ドskip()もある。
 また,バイトデータ入力ストリームによっては,マーク機能を持つものもある。これらマーク
機能に関するメソッドもInputStreamクラスで定義されている。

コラム
 マーク機能とは,ストリームからデータを入力しているとき,マークを設定すると,後でそのマーク位置
まで戻って再びデータを再入力できるようにする機能である。
mark()メソッドを呼び出すと,現在の読み
込み位置にマークが設定される。そして,
reset()メソッドを呼び出すと,最後に設定したマーク位置まで
読み込み位置が戻され,そこから再度読み込みが開始される。
 実際には,マークを設定した時点からreset()が呼び出されるまでに入力したデータをストリームオブジェ
クトが保存しておくことで,このマーク機能は実現されている。最大どれだけのデータを憶えておくように
するか指定できるmark()メソッドも用意されている。すべてのストリームでマーク機能が使えるわけではな
く,マーク機能を持っているストリームでは,
markSupported()メソッドがtrueを返すようになっている。

●バイト入力用の真性ストリーム
 では,Fig.2にもどって,今度はInputStreamのサブクラスを見てみよう。Fig.2を再掲する。

 InputStreamのサブクラスは,2つのグループわけられる。ひとつは,本当の意味でのストリ
ームで,ここでは便宜的に“真性ストリーム”と呼ぶことにする。もうひとつのグループは,
他のストリームに付加機能をつけるためのストリームで,ここで便宜的にラッパストリームと呼
ぶことにする。
 バイトデータ入力ストリーム系の真性ストリームとしては,ByteArrayInputStream,FileInputStream,
PipedInputStreamの3つがあることが上図で分かる。この3つをまず紹介する。

○ByteArrayInputStream
 ByteArrayInputStreamは,メモリ内にあるbyte型配列を入力元とするストリームである。
ストリームは必ずしも外部機器に関連づけられる必要はなく,ByteArrayInputStreamのよう
にメモリ内に存在する対象と結びつけることもできるのである。
 このようなメモリ内に入出力先を持っているストリームを,メモリ内ストリームと呼ぶ。
メモリ内ストリームを使えば,ストリームを使った入出力の処理が実際にどのように行われて
いるのか確かめることが簡単にできるので,ストリーム処理のデバッグなどに便利である。
ByteArrayInputStreamは,InputStreamで定められているメソッドの他に,Table 2-(1)に示
す2つのコンストラクタを持っている。

○PipedInputStream
 また,PipedInputStreamは,スレッド間のデータのやりとりに使われるメモリ内ストリー
ムである。スレッド(thread)とは,同じプログラムの中で,同時並行に動作するメソッド(およ
びその環境)のことである。PipedInputStreamについては,メソッドを解説するときに取り上
げる予定であるので,ここではこのようなストリームが存在していることだけを憶えておくだ
けでよい。

○FileInputStream
 FileInputStreamは,ファイルからのバイトデータの読み込みを行う入力ストリームである。
主なコンストラクタとメソッドをTable 2-(2)に示す。コンストラクタの引数に使われるクラス
Fileは,ファイルを操作するためのユーティリティ(道具)を集めたクラスである。また,クラス
FileDescriptorは,特定のファイルを指定するファイル記述子と呼ばれるものである。ファイル
パス名の他に,これらを使ってファイルを指定・オープンすることも可能になっている。
 また,getChannel()は,JDK1.4で導入された新しい入出力の仕組みで,ストリームのかわり
に使われるチャンネル(channel)を生成するメソッドである。この新しい入出力はjava.nioパッ
ケージにまとめられている。実は現在では,チャンネルを入出力に使うのが推奨されているが,
Java誕生時より用意されており,未だによく使われているストリームの方を本授業では解説する。
(興味が有れば是非チャンネルについても自分で調べてみよ)

【バイト入力用のラッパストリーム】
 前述のように,ラッパストリームとは,他のストリームに付加機能をつけるストリームである。
ラッパストリームが元になるラッパストリームに機能を付加する仕組みを下図Fig.3に示す。ま
ず,元になるストリームオブジェクトをラッパストリームオブジェクトが参照している。

 そして,ラッパストリームのメソッドは,元のストリームのメソッドを呼び出している。
たとえば,ある利用者オブジェクトのメソッドが,ラッパストリームのread()メソッドを呼び
出すと(Fig.3-<①>),ラッパストリームのread()メソッドは,参照している元のストリーム
のread()メソッドを呼び出す。元のストリームのread()メソッドは,ストリームからデータ
を取り出し(Fig.3-<3>),それを呼び出し元であるラッパストリームのread()メソッドに返す
(Fig.3-<4>)。ラッパストリームのread()メソッドは,返された入力データを呼び出し元の
メソッドに返す(Fig.3-<5>)。
 同様に,利用者オブジェクトのメソッドがラッパストリームのclose()メソッドを呼ぶと
(Fig.3-<6>),間接的に元のストリームのclose()メソッドが呼ばれることになる(Fig.3-<7>)。
 このような仕組みによって,利用者オブジェクトにとっては,ラッパストリームが元のスト
リームを“包んで(wrap)”いるように見える
。そのために,ラッパ(wrapper stream)ストリーム
と呼ぶのである。一般に,このラッパストリームクラスのように,他のクラスをラップする
クラスをラッパクラスとか,単にラッパと呼ぶ
。なお,当然のなりゆきとして,ラッパス
トリームは元のストリームを引数とするコンストラクタを持つ
ことになる。
 そして,ラッパストリームは,元のストリームのメソッドを呼びながら,様々な追加処理
を行う機会を持つことができる。たとえば,Fig.3のラッパストリームのread()メソッドは,
元のストリームのread()メソッドが返してきたデータになにかしらの加工を施して,利用者
オブジェクトに加工結果のデータを返すように定義することが可能である。
 つまり,利用者オブジェクトからすれば,このラッパストリームは,元のデータストリーム
に特定のデータ加工機能を付加したものに見えるということである
。たとえば,暗号化された
データを復号化するラッパストリームを考えることが出来る(下図)。

 このラッパストリームによる機能追加の仕掛けは,入力ストリームに対してだけではなく,
出力ストリームにも有効である。
 では,Fig.2でふたたびバイト入力用のラッパストリーム群を見ていくことにしよう。バイト
入力用のラッパストリーム群には,InputStreamをラップするラッパストリームとして,
  ・FilterInputStream
とそのサブクラスである
  ・DataInputStream
  ・BufferedInputStream
  ・PushbackInputStream
と,そのInputStreamの直接のサブクラスである
  ・ObjectInputStream
  ・SequenceInputStream
の計6個が用意されている。次に,これらの6つのクラスについて簡単に説明しよう。

○FilterInputStream
 FilterInputStreamは,まさに元ストリームから入力するデータを加工(filtering)することを
目的としたラッパストリームである。元になるInputStream型ストリームを引数とするコンス
トラクタを持っている(Table 2-(3))。
 FilterInputStream自身は何も加工を行わないが,サブクラスのためにデータ加工のための骨
組みをあたえている。そして,実際に様々な加工をするように定義されているのが,3つのサブ
クラス
 ・DataInputStream
 ・BufferedInputStream
 ・PushbackInputStream
である。

○DataInputStream
 DataInputStreamは,主にプリミティブ型を中心としたデータ値をストリームから読み込む
機能を,元のストリームに付加するラッパクラスである。そのため,プリミティブ型を中心と
したデータ値を読み込むためのメソッドを集めたインタフェイスDataInputを実装している。
インタフェイスDataInputの詳細を,下表Table 3に示す。その他,コンストラクタや独自のメ
ソッドを,Table 2-(4)に示す。


○BufferedInputStream
 BufferedInputStreamは,元の入力ストリームにバッファ機能を付加するラッパクラスであ
る。ディスク装置などの入出力機器のデータアクセス速度は,メモリ上のデータへのアクセス
速度にくらべ,圧倒的に遅い。そのため,少量のデータを頻繁に読み込むなどすると,非常に
時間がかかってしまう。
 そこで,バッファ(buffer)と呼ばれるメモリ上の領域に,まとまった量のデータをあらかじめ
いっきに読み込んでおき,実際に読み込むときには,そのバッファからデータを入力するよう
にすれば,入力処理を高速化することができる。BufferedInputStreamオブジェクトは,バッファ
領域を確保して,バッファ機能を提供してくれるのである。積極的に使うべきラッパストリーム
と言える。コンストラクタをTable 2-(5)に示す。

○PushbackInputStream
 BufferedInputStreamは,元の入力ストリームにプッシュバック(push back)機能を付加する
ラッパストリームである。プッシュバックとは,「一度読み込んだデータを読まなかったこと」
にする操作のことである。あるバイトデータを読み込んだ直後にプッシュバック操作を行うと,
次にバイトデータを読み込むとまた同じバイトデータを読み込むことになる。Table 2-(6)にコ
ンストラクタと独自メソッドを示す。プッシュバック操作を行う3つのunread()メソッドが用意
されている。

○ObjectInputStream
 Javaには,オブジェクトをデータ化して,ストリームに書き込んだり読み込んだりする機能
がある。オブジェクトをデータ化してストリームに出力することを,直列化(シリアライズ,serialize)
と呼び,そういやって直列化されたデータをストリームから読み込んでオブジェクトを再構築す
ることを直列化復元(デシリアライズ,deserialize)と言う。
 ObjectInputStreamは,InputStreamの直接のサブクラスで,ストリームから読んだバイトコ
ードを直列化復元して,オブジェクトに戻す機能を持ったストリームである。こういった特殊な
用途のために,Fig.2に示してあるとおり,オブジェクトを読み込むために必要なインタフェイス
ObjectInput,ObjectStreamConstantsを実装している。
 ObjectInputStreamのコンストラクタと,直列化復元を行うメソッドreadObject()の概要を,
Table 2-(7)に示す。

○SequenceInputStream
 SequenceInputStreamは,複数の入力ストリームを結合して,ひとつの入力元として扱うこと
のできるストリームである。このストリームについては,本授業では詳しくはふれない。

【バイトデータ出力系統ストリーム】
 さて,次にバイトデータ出力系のストリームについて見ていこう。下図Fig.4にバイトデータ
出力系統ストリームの一覧を示す。Fig.2のバイトデータ出力系統ストリーム一覧と比べると,
多くの類似点を見つけることができる。この類似点を意識することが,ストリームの全体像の理
解に役立つ。
 たとえば,バイトデータ出力系も真性ストリームとラッパストリームにグループ分けできるし,
ByteArray〜,Piped〜,File〜,Filtered〜,Data〜,Buffered〜,Object〜で始まるストリーム
クラスが,バイトデータ入力系にもバイトデータ出力系にも存在する,などなどである。以上の
ような理由により,Fig.2とFig.4をじっくり比較してみることをすすめる。


●スーパークラスOutputStream
 まず,バイト入力ストリームにInputStreamというスーパークラスがあったように,バイト
データ出力系統ストリームにも,OutputStreamというスーパークラス(抽象クラス)がある。
 OutputStreamのメソッドを下表Table 4に示す。

 バイトデータを書き込むメソッドとして,3つのwrite()がある。flush()は,出力系ストリーム
に特徴的なメソッドである。前述したように,入出力ではアクセス高速化のためにバッファを利用
している場合が多い。そのため,Javaのレベルでバッファリングを明示的に行っていなくても,
オペレーティングシステム(OS)のレベルではバッファリングされている場合も多いのである。
 このバッファに出力された内容を,実際に出力先に送信・書き出す動作をフラッシュ(flush)と
言う。そして,フラッシュを行うメソッドがflush()なのである。
 もし,バッファがフラッシュされる前にプログラムが異常終了したり,出力先のディスク装置
が外されたりすると,プログラム上ではデータを書き込んだつもりなのに,実際にはディスクに
記録されていなかった,というような事態が生じてしまう。
 こういったことを防止し,確実に出力先にデータを書き込むためにも,適宜メソッドflush()を
呼び出して,意図的にバッファのフラッシュを行うことが重要
である。ただし,あまり頻繁にフ
ラッシュすると,バッファを利用している意味が無くなり,アクセス速度が低下しますので注意
が必要である。
 そして,クローズを行うメソッドclose()ももちろん用意されている。close()は,フラッシュを
行ってから,ストリームを閉じてくれる
(サブクラスでオーバライドするときには,当然そのよう
に定義する必要がある)。


●バイト出力用の真性ストリーム
 バイトデータ出力用のストリームは,Fig.4に示されているとおり,OutputStreamのサブクラス
として定義されている。そしてバイトデータ入力系と同様に,真性のストリームとラッパストリー
ムに分けることができる。まず,真性ストリームの方から紹介していこう。

○ByteArrayOutputStream
 ByteArrayOutputStreamは,入力系のByteArrayInputStreamに対応するもので,byte型配列
を出力先にするメモリ内ストリームである。コンストラクタと独自メソッドをTable 5-(1)に示す。
配列に出力されたデータを様々な形式で取り出すメソッドが用意されていることがわかる。

○PipedOutputStream
 PipedOutputStreamは,入力系のPipedInputStreamに対応するもので,スレッド間のデータ
のやりとりに使われるメモリ内ストリームである。PipedOutputStreamも,PipedInputStreamと
同様に,本授業では詳しくは触れない。

○FileOutputStream
 FileOutputStreamは,入力系のFileInputStreamに対応するもので,ファイルへバイトデータ
を書き込む出力ストリームである。主なコンストラクタとメソッドをTable 5-(2)に示す。書き込
み先としてオープンすると,指定したファイルが無いときには新しくファイルを作成する。また,
既に存在するファイルを指定すると,そのファイルの中身を消去して,ファイルの先頭から書き
込みを開始するようにオープンする。つまり,ファイルを「上書き」する。場合によっては,既
存のファイルの末尾から「追記(append)」する形でデータを書き込みたいときがある。その場合,
コンストラクタの第2引数にtrueを渡す。

●バイト出力用のラッパストリーム
 次に,バイトデータ出力系のラッパストリームを見ていこう。Fig.4でわかるとおり,ラッパス
トリームは,FilterOutputStreamとそのサブクラスである
 ・DataOutputStream
 ・BufferedOutputStream
 ・PrintStream
そして,OutputStreamの直接サブクラスである
 ・ObjectOutputStream
の計5個が用意されている。これらは,OutputStream型ストリームをラップして,特定の機能を
付加する。では,これらのクラスを簡単に紹介していこう。

○FilterOutputStream
 FilterOutputStreamは,入力系のFilterInputStreamに対応するもので,OutputStream型の
出力ストリームに,データ加工(フィルタ)機能を付加する。Table 5-(3)にコンストラクタを示す。
FilterOutputStream自体は何の機能も付加しないが,サブクラスのためにデータ加工のための骨
組みをあたえている。そして,実際に様々な加工をするように定義されているのが,3つのサブク
ラスDataOutputStream,BufferedOutputStream,PrintStreamである。

○DataOutputStream
 DataOutputStreamは,入力系のDataOutputStreamに対応するもので,主にプリミティブ型
を中心としたデータ値をストリームへ書き込む機能を,元のOutputStream型ストリームに付加
するラッパクラスである。そのため,プリミティブ型を中心としたデータ値をバイトデータとし
て書き込むためのメソッドを集めたインタフェイスDataOutputを実装している。インタフェイス
DataOutputの詳細を,下表Table 6に示す。また,コンストラクタと独自メソッドをTable 5-(4)
に示す。


○BufferedOutputStream
 BufferedOutputStreamは,入力系のBufferedOutputStreamに対応するもので,元のOutputStream
型ストリームに,バッファ機能を付加するラッパクラスである。出力速度の改善のためにも,
BufferedInputStreamと同様,積極的に使うべきラッパストリームと言える。コンストラクタ
をTable 5-(5)に示す。

○PrintStream
 PrintStreamは,OutputStream型ストリームに,プリミティブ型や文字列の値を,文字表現
としての値を持つバイトデータ
として出力する機能を,元のOutputStream型ストリームに付加
するラッパクラスである。PrintStream型ストリームで馴染みが深いのは,
 System.out
であろう。これは,Systemクラスのstaticフィールドで,標準出力(standard output)と呼ばれ
るオープンずみの特別な出力ストリームオブジェクトである。print()メソッドやprintln()メソッ
ドを使って変数などの値を表示した経験から,どのような機能を持っているかは,だいたいお分
かりだろう。コンストラクタと独自メソッドをTable 5-(6)に示す。
 しかし,バイトデータ出力系ストリームであるPrintStreamよりも,文字出力の面でより改善
された文字データ出力ストリーム系のPrintWriterがあるので,標準出力としてSystem.outを使
うとき以外は,PrintWriterを使うべき
である。なお,PrintStreamに対応するバイトデータ入力
系のストリームは用意されていない。

○ObjectOutputStream
 ObjectOutputStreamは,入力系のObjectInputStreamに対応するもので,元のOutputStream
型ストリームに,オブジェクトを直列化して,ストリーム書き込む機能を付加するラッパクラス
である。Fig.4に示してあるとおり,オブジェクトを読み込むために必要なインタフェイスObjectOutput,
ObjectStreamConstantsを実装している。コンストラクタと,直列化を行うメソッドwriteObject()
の概要を,Table 5-(7)に示す。

【バイトデータ入出力の使用例】
 それでは,いよいよ実際にバイト入出力系ストリームを使用してみよう。なお,ストリームの
機能を使用するにあたって注意しなければならない点として,例外の扱いがある。基本的に,入
出力系のメソッドのほとんどは,なにか処理に支障が出るとIOExceptionおよびそのサブクラス型
の例外を投る。Table 1〜6に,どのような例外が投げられるかを示してあるので,よく確認して
おくこと。
 List 1は,filestreamtest1.dataという名前のファイルにバイトデータを書き込み,続けてその
ファイルからバイトデータを読み込むプログラムである。
注:実行時に既にfilestreamtest1.dataという名前のファイルがあると内容を上書きするので,
もし同名の重要なファイルがある場合には,プログラム内のfilestreamtest1.dataを別の名前
に変えて実行するように。

List 1 (StreamTest1.java)

 さて,List 1だが,List 1-<①>で,java.ioパッケージをインポートしている。そして,List 1-<2>
から指定されたファイルにbyte型データを書き込むメソッドwriteBytesToFile()が定義されている。
例外指定でIOExceptionを投げることが明示されていることに注意。
 List 1-<3>では,ファイルストリームとそれをラップするバッファストリームの変数がnull初期化
をともなって宣言されている。そして,tryブロックの中で入出力処理が行われることになる。List 1-<4>
では,引数nameで指定されたファイルをオープンし,List 1-<5>では,それをラップするBufferedOutputStream型
オブジェクトbosが生成されている。bosのオープンが成功したら,バイトデータをwrite()メソッド
で書き込んで,基本的な入出力処理が終わる。
 もしこの過程で例外が発生すれば,List 1-<7>で例外をキャッチして,適切な処置を行う。最後に,
finallyブロックで,例外が発生したかどうかにかかわらず,ストリームをクローズする(List 1-<8>,<9>)。
このクローズ時に例外が発生すると,例外はこのメソッドの外に伝搬していく(そのために,List 1-<2>
で例外指定をしてある)。
 ここで,重要なことが2点あげられる。まず,List 1-<3>でtryブロックの前でストリーム変数をnull
に初期化し,finallyブロックでクローズするときに,このストリーム変数がnullでないか確かめてから
クローズしていることである(List 1-<8>,<9>)。もし,List 1-<4>,<5>のいずれかで例外が発生す
ると,ストリーム変数fos,もしくはfosとbosの両方がnullのままになるので,List 1-<8>,<9>の
ように,nullでないか確かめてからclose()メソッドを呼ばないといけないのである。
 そして,List 1-<8>,<9>ではラッパストリーム→元のストリームの順でクローズしていることが重要
である。Fig.3を見ればわかるが,元のストリームは,ラッパストリームから一方的に参照されるだけで,
ラッパストリームにラップされていることを知らない。そのため,元のストリームをラッパより先にクロ
ーズすると,ラッパストリームが管理しているバッファがフラッシュされないままになってしまう
。よって,
まずラッパストリームを先にクローズしなくてはならないのである。
 前述したように,ラッパストリームをクローズすれば,通常は自動的に元のストリームもクローズされ
る。そうすると,正常に入出力が行われた場合は,List 1-<8>,<9>で元のストリームは2回クローズさ
れることになる。しかし,心配は無用である。すでにクローズされたストリームを再度クローズしてもエ
ラーにならない
のである。そのため,List 1-<4>で例外が発生してラッパストリームだけが生成されなか
った場合のことも考えて,List 1-<9>で元のストリームをクローズしておくべきなのである。
 List 1-<10>から始まっている読み込み用メソッドreadBytesFromFile()の定義でも同様の注意が必要
である。List 1-<11>〜<17>の一連の流れをList 1-<2>〜<9>と比べてみて欲しい。さて,main()メソッ
ドでは,List 1-<18>で元データを格納する配列と,データを読み込む配列を宣言している。List 1-<19>,<20>
で,データの書き込みと読み込みをしているわけである。最後に,クローズ時に例外が発生したときの対
処として,List 1-<21>にchatchブロックが用意されている。

【次回は】

 次回もひきつづき,入出力処理について解説していく。














戻る