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


●入出力処理(後編)「もっとJavaを学ぶ 第5回 (2004年1月 )」

 今回は,入出力処理のしめくくりとして,RandomAccessFileによるランダムアクセス
ファイルの使い方,Fileによる抽象パスの表現とファイル操作,そしてファイル記述子
FileDescriptorの用途を解説する。

【ランダムアクセスファイル】
 前々回に,入出力には大きく2種類あるということを紹介した。ひとつは,一連のデー
タがひとつひとつ順次に入出力されるタイプのもので,Javaではこの種の入出力を,スト
リームという概念で一括して扱っている(Fig.1)。

ストリームでは,データが順序よく入出力すればいいだけなので,多くの入出力機器をこ
のストリームの概念で扱うことができる。たとえば,キーボードやモニタ(出力端末)も
ストリームとして扱うことが可能である。
 もうひとつのタイプは,メモリのように好きな位置のデータにアクセス(ランダムアクセ
ス)できる入出力特性を持っているものである。ランダムアクセスという強力なアクセスを
提供できる出力機器は少なく,事実上,ファイルだけと考えることができる。
 Javaでは,ファイルに対するランダムアクセスによる読み書き機能を提供するRandomAccessFile
クラスが提供されている。ランダムアクセスファイルの概念をFig.2に示す。ランダムアク
セスファイルは,バイト単位でアクセス可能な配列と考えるとわかりやすいだろう(Fig.2-(1))。

 Fig.2-(1)で図示してあるように,ランダムアクセスファイルでは,次に読み込みや書
き込みを行う位置を,ファイルポインタというもので管理している。Fig.2-(1)の例では,
第4バイト目にファイルポインタがある(先頭のバイトが第0バイトから始まっていること
に注意)。
 このファイルポインタは,前後に好きなだけ自由に移動させることができるので,ラン
ダムアクセスが可能になっているのである。このファイルポインタを移動させる操作を,
シーク(seek)と呼ぶ。ファイルポインタの位置は,読み書き動作をすると自動的に進んで,
次に読み書きする位置を指すようになる。Fig.2-(2)は,Fig.2-(1)の状態からint型データ
(4バイト)を書き込んだ直後の様子である。第4〜7バイトにデータが書き込まれた後,フ
ァイルポインタは4バイト進んで第8バイトを示すように設定され,次の読み書き動作のた
めの備えを行う。
 また,ランダムアクセスファイルが一般的なストリームと異なる点としては,書き込み
可能なファイルであれば,オープンしたファイルに対して,読み書きを同時に行うことが
可能であることがあげられる。ストリームの場合は,入力用と出力用に別々のストリーム
をオープンする必要があったが,ランダムアクセスファイルはそんな必要は無いというこ
とである。
 なお,ランダムアクセスファイルも大きい意味でストリームと呼ぶ場合もあるので,注
意すること。

【RandomAccessFileの詳細】
 では,RandomAccessFileクラスの詳しい説明をしよう。Fig.3にRandomAccessFile
のクラス階層を示す。RandomAccessFileは,DataInputインタフェイスとDataOutput
インタフェイスを実装しているだけである(図では省略しているが,もちろんObjectを継
承している)。このように,RandomAccessFileは一般的なストリームのクラス階層から
は切り離されている。


 Table 1に,RandomAccessFileのメソッドを示す。

まず,コンストラクタだが,ファイルを指定するほかに,オープンモードを指定する。
読み込み専用モードでファイルをオープンする場合は,
  "r"
という文字列を第2引数に渡す。そして,読み込み・書き込み兼用モードでオープンする
場合は,
  "rw"
を第2引数に指定する。読み込み・書き込み兼用モードでオープンしようとしたときに,
指定されたファイルが存在しない場合は,新しいファイルを作成しようとする。
 間違ったモードの指定を行うと,IllegalArgumentException例外を投げる。IllegalArgumentException
は,RuntimeExceptionのサブクラスなので,いわゆる“チェックされない例外”(第2回
参照)である。
 RandomAccessFileの独自メソッドとしては,ファイルをクローズするclose(),オー
プンしたファイルに結びつけられたチャンネルを返すgetChannel(),オープンしたファ
イルのファイル記述子を返すgetFD()がある。
 シークに関しては,指定された位置にファイルポインタを移動するseek()や,現在の
ファイルポインタの位置を返すgetFilePointer()がある。
 また,ファイルの長さを返すlength(),ファイルの長さを強制的に変更するsetLength()
が用意されている。setLength()で,より短くファイルを切りつめることもできる。この
とき,切り捨てられた部分にファイルポインタが位置していたら,ファイルポインタは
  新しいファイルの長さ+1
の位置に設定される。ファイルをより長く拡張することもできる。このとき,拡張され
た部分の値はどんな値になるかは不明である。このように,ファイルの長さを調節でき
るのも,ランダムアクセスファイルの大きな特徴と言える。一般的なストリームでは長
さを仮定できないので,このような芸当は不可能である。
 読み込むための独自メソッドとしては,バイト単位で読み込む3つのreadメソッドが用
意されている。
 その他,基本データ型を入出力するためにDataInputとDataOutputから継承したメソッ
ドがある。

【RandomAccessFileの使用例1】
 RandomAccessFileを実際に使用した例を,List 1に示す。List 1で定義されているメ
ソッドUseRandomAccessFile()は,一連のdouble型のデータをランダムアクセスファイ
ルに1000個保存し,100個おきのデータを読み出して,それを10倍して書き戻す,とい
うものである。RandomAccessFileTest1.dataという名前のファイルがすでに存在する
と,上書きしようとするので,同じ名前の大事なファイルがあるときには,別の名前に変
更してから実行するように注意すること。
List 1 RandomAccessFileTest.java

 まず,tryブロックの前で,RandomAccessFile型の変数rafを宣言しておく(List 1-<①>)。
tryブロックの中でnameで指定された名前のファイルを,読み出し・書き込み兼用モード
でオープンする(List 1-<2>)。
 List 1-<3>のforループで,double型データを1000個書き込む。具体的には,0.0か
ら始まって,0.01ずつ増やしつつファイルに書き込んでいる。書き込みには,writeDouble()
メソッドを使用している。
 書き込んだ1000個のデータのうち,0個目,100個目,200個目,…,900個目,と
いうように,100個おきに10個だけ読み込んで,その値を表示しているのがList 1-<4>
のforループである。ループ用の変数iは0から999まで1ずつ増やされてされていく。100
個おきにdouble型データ(8バイト)を読み込むために
  raf.seek( i * 8 );
として,読み込むべき位置にファイルポインタを移動させている。double型データの読
み込みには,readDouble()メソッドを使っている。このList 1-<4>のforループが実行さ
れると,Fig.4-(1)のように表示され,確かにファイルにデータが書き込まれており,その
うちの100個おきのデータを読み込むことに成功していることがわかる。

 次に,List 1-<5>の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 1-<6>で,実際に100個おきのデータを読み込んで表示している。この部分が
実行されると,Fig.4-<2>のように表示され,実際にファイル上の1000個のdouble型
データのうち,100個おきのデータが10倍されていることがわかる。
 List 1-<7>は例外のキャッチ,List-<8>はファイルのクローズである。main()メソッ
ドのtryブロック(List 1-<9>)の中でメソッドUseRandomAccessFile()を呼び出す。投
げられた例外は,List 1-<10>でキャッチするようにする。
 List 1の例では,ランダムアクセスファイルをdouble型の配列のように扱った。しか
し,様々な型のデータを混合して書き込むことももちろん可能である。

【RandomAccessFileの使用例2】
 ところで,RandomAccessFileは,Fig.3に示すようにストリームとは別のクラス階層を
持っている。ストリームの中には便利な機能を持つものがある。たとえば,オブジェクト
を読み書きするObjectInputStreamやObjectOutputStreamなどである。つまり,そのま
までは,RandomAccessFileに対してオブジェクトを書き込むことはできない,というこ
とになってしまう。
 しかし,あきらめることはない。ここでは,ランダムアクセスファイルにオブジェクト
を書き込む方法を紹介する。
 しかけは簡単である。ObjectInputStreamやObjectOutputStreamはそれぞれ,バイト
配列ベースのメモリ内ストリームByteArrayInputStream,ByteArrayOutputStreamを
ラップすることができる。つまり,オブジェクトはバイト配列に書き込むこともできるし,
バイト配列から読み込むことも可能なのである。一方,RandomAccessFileは,バイト配
列の内容を書き込んだり,バイト配列の内容を書き込むことができる。つまり,バイト配
列を媒介にして,ランダムアクセスファイルに対してオブジェクトの読み書きを実行する
ことができるのである。
 実際に読み書きの処理を紹介する前に,ランダムアクセスファイルにオブジェクトを書
き込む際に,どのように格納するか,そのフォーマット(形式)を決めておこう。Fig.5を見
てみよう。


 まず,ファイルの先頭に,オブジェクトの登録表(エントリテーブル)を用意しておく。
登録表の各要素は2つのint型データからなり,最初のint型データはオブジェクトのファ
イル上の位置,2番目のint型データはその長さを表している。たとえば,Fig.5の場合,
最初(第0)のオブジェクトは,ファイル上で800バイト目から記録されていて,その長さ
は72バイトである。第1バイトは,ファイル上で872バイト目から記録されていて,そ
の長さは74バイトである,ということになる。
 この登録表は,オブジェクト100個分まで用意されていて,その長さは800バイトであ
る(int型データが4バイト,1オブジェクトに2つのint型データを使用するので,100オブ
ジェクトで計800バイト必要になる)。そして,実際のオブジェクトのデータは,登録表の
後ろに記録されていく。
 では,実際にオブジェクトをランダムファイルに書き込んだり,その逆に読み込んだり
する例を紹介しよう(List 2)。オブジェクトの格納法は,Fig.5の方法に準じることとする。
なお,List 2は,RandomAccessFileTest2.dataという名前のファイルに書き込むように
なっているので,同名の大事なファイルがある場合には,名前を適切に変更してから実行
すること。
List 2 RandomAccessFileTest2.java


 List 2-<①>は,シリアライズの対象になるオブジェクトのクラスPersonである。List 2
-<2>から始まるwriteObjectToByteArray()が,Person型オブジェクトpを指定されたオ
ープン済みのランダムアクセスファイルrafに書き込むメソッドである。List 2-<3>で,
バイト配列ベースのメモリ内ストリームByteArrayOutputStream型の変数baosと,オブ
ジェクト出力用のObjectOutputStream型変数oosを宣言しているところである。List 2-
<4>では,出力した結果を得るためのバイト型配列bytesと書き込まれたオブジェクトの
長さを格納する変数lenが宣言されている。
 List 2-<5>では,ByteArrayOutputStream型ストリームをオープンし,さらにそれを
ObjectOutputStream型ストリームoosでラップしている。これで準備完了。List 2-<6>
では
  oos.writeObject( p ); oos.flush();
として,oosに対してオブジェクトpが書き込んでフラッシュする。ここでバイト配列出
力ストリームbaosにオブジェクトが書き込まれることになる。そして,List 2-<7>の
  bytes = baos.toByteArray();
  raf.write( bytes );
でbaosに書き込まれた内容をバイト型配列bytesに取り出して,それをランダムアクセス
ファイルrafに書き込んでいる。List 2-<8>は書き込んだ長さを変数lenに代入していると
ころである。List 2-<9>はcatchブロックとfinallyブロックである。finallyブロックでは
最後に書き込んだ長さlenを返している。
 List 2-<10>から始まるreadObjectFromByteArray()は,長さlenバイトのデータをラン
ダムアクセスファイルから読み込んでデシリアライズして返すメソッドである。List 2-<11>
は読み込みに使用する長さlenのバイト型配列bytesを生成している。List 2-<12>では,
バイト型配列ベースのメモリ内入力ストリームByteArrayInputStream型の変数baisと,オ
ブジェクト入力用ストリームObjectInputStream型の変数oisを宣言している。
 では,読み込み作業を開始しよう。まず,List 2-<13>でバイト型配列bytesにランダム
アクセスファイルから一連のバイトデータを読み込む。このデータを読み込んだバイト型配
列bytesを元にして,ByteArrayInputStream型ストリームbaisをオープンし,そのbaisを
ラップするObjectInputStream型ストリームoisをオープンしているのがList 2-<14>であ
る。そして,List 2-<15>の
  obj = ois.readObject();
で,oisからオブジェクトをデシリアライズして,objに格納している。List 2-<16>は,
catchブロックとfinallyブロックで,最後にobjをPerson型に変換して返している。
 以上のwriteObjectToByteArray()メソッドとreadObjectFromByteArray()メソッドを利
用して実際に書き込みと読み込みを行っているのが,List 2-<17>から始まるUseRandomAccessFile()
メソッドである。List 2-<18>で,ランダムアクセスファイルrafをオープンする。List 2
-<19>でランダムアクセスファイルの先頭に登録表を作成している。List 2-<20>では,
定数tablesizeを登録表の長さ(800バイト)で初期化している。
 List 2-<21>ではファイルポインタが登録表の直後を指すようにシークし,List 2-<22>
では実際にPerson型オブジェクトを3つ格納している。それぞれの長さはlen1〜len3に代
入される。つづいて,ファイル先頭にもどって(List 2-<23>),登録表にそれぞれのオブ
ジェクトの格納位置と長さを記録している(List 2-<24>)。
 List 2-<25>から始まるfor文で3つのオブジェクトを読み込んでいる。List 2-<26>で
登録表の第i番目のオブジェクトの格納位置を記録している場所にシークし,List 2-<27>
でオブジェクトの登録場所とデシリアライズされたオブジェクトの長さを読み出している。
List 2-<28>では,登録場所と長さを表示する。
 List 2-<29>で登録場所にシークし,List 2-<30>でreadObjectFromByteArray()メソッ
ドを呼び出してオブジェクトを読み込む。List 2-<31>は,読み込んだPerson型オブジェ
クトのintroduce()メソッドを呼び出す。List 2-<25>〜<31>のforループを実行すると,
Fig.6のように表示され,ランダムアクセスファイルに対してオブジェクトを読み書きする
ことに成功していることがわかる。List 2-<32>はcatchブロックとfinallyブロックである。

 main()メソッドでは,List 2-<33>でUseRandomAccessFile()を呼び出して読み書きを
実行している。List 2-<34>はcatchブロックである。
 以上のように,少し工夫をすることで,ランダムアクセスファイルに対してもオブジェ
クトを読み書きすることが可能である。ところで,List 2の実行結果(Fig.6)を見てみると,
同じPerson型のオブジェクトでも,オブジェクトごとの長さが異なっていることがわかる。
これは,Person型オブジェクトが長さが可変であるString型のフィールドを持っているた
めである。この例のように,オブジェクトは同じ型でも,シリアライズした結果の長さは
オブジェクトごとに変わることがある。このために,Fig.5のようにオブジェクトの記録位
置と長さを登録表にひかえておいたのである。
 このように長さが可変のデータをランダムアクセスファイルに記録するときは注意が必要
である。List 1はdouble型のデータを読み込んで,その値を変えた上で再び同じ位置に変更
後の値を書き込んでいた。これは,double型の長さが固定であるために可能だったのである。
一方,Personのようにオブジェクトの長さが可変であるデータの場合は,オブジェクトの長
さが変わった後でファイルの同じ位置に書き戻したら,後続のデータを上書きして破壊して
しまう可能性がある。
 こういったケースに対処するには,ランダムアクセスファイルの「すべての内容」をいっ
たんメモリに読み込んで,データ値の変更をした後,ランダムアクセスファイルを最初から
作成し直す,などしなければならない。

【Fileクラス】
 さて,次にFileクラスを紹介しよう。Fileクラスはストリームではない。ファイルを指定
する場合,多くのオペレーティングシステムでは,パス名(path name)を使う。しかし,
オペレーティングシステムによってパス名の表現方法は異なる。
 たとえば,UNIXではすべてのファイルはルートディレクトリ(/)以下に繋がるディレクト
リ木構造に属している。そして,ディレクトリ階層は/で区切られている。dir1,dir2,...,
dirNをディレクトリ名とすると,特定のファイルを指定するパス名は,
  /dir1/dir2/dir3/ファイル名
というように,ルートディレクトリから特定のファイルまでのつながりを,/で区切って表
現する。
 一方,Windowsでは,ファイルシステムはA:やC:といったドライブごとにディレクトリ木
構造を持っており,ディレクトリ区切りには'\'(欧米系のフォントでは逆スラッシュの文字
で,日本語用フォントでは半角の円マーク文字で表示されることもある)を使う。たとえば,
C:ドライブにあるファイルのパス名は
  C:\dir1\dir2\dir3\ファイル名
というように表現する。
 Javaは,どのプラットフォーム上でも同じように動作することをその至上命題としている。
そのため,プラットフォームによってパス名の表現が異なることは大きな問題となる。そこ
で,導入されたのがFileクラスである。Fileクラスは,プラットフォームに依存しない,抽象
的なパス名(抽象パス名)を表すクラスなのである
。以上のことから,パス名でファイルを指
定するより,いったんFile型オブジェクトを生成して,それを使うようにする方が良い。
 また,Fileクラスは,ファイルやディレクトリに関する基本操作も提供してくれる。たとえ
ば,ファイル名の変更やディレクトリの作成,などなどである。

●Fileの機能
 それでは,Fileクラスのメソッドとpublicフィールドを紹介していこう(Table 2)。

まず,コンストラクタだが,パス名文字列pathnameからFile型オブジェクトを生成するものや,
親ディレクトリとファイル(ディレクトリ)名の組み合わせで対象となるファイル(ディレクトリ)
を表すFile型オブジェクトを生成するものがある。また,URIで指定されたファイル(ディレ
クトリ)を表すFile型オブジェクトを生成するコンストラクタも用意されている。
 ファイル(ディレクトリ)が,読み書き可能かを調べるメソッドとして,canRead()canWrite()
が用意されている。compareTo()メソッドやequals()メソッドは,File型オブジェクトどう
しの比較に使う。
 creatNewFile()メソッドは,このFile型オブジェクトが表しているファイルを実際に作成し
ようとする。creatTempFile()メソッドは,作業用の一時ファイルを生成する。たとえば,
  createTempFile("MyTemp", ".temp");
として呼び出すと,一時ファイル用のディレクトリ(たいていオペレーティングシステムごと
にデフォルトの場所が決まっている)内に
  MyTemp*******.temp
という形式の一時ファイルを作成する(*******の部分は一意,すなわち他と重ならない唯一
の番号など)。一時ファイルを作成するディレクトリを明示的に指定する版もある。
 delete()メソッドは,このFile型オブジェクトが表すファイル(ディレクトリ)を削除する。
deleteOnExit()メソッドは,仮想マシン終了時にこのFile型オブジェクトが表すファイル(デ
ィレクトリ)を削除するように指定する。ファイル(ディレクトリ)が存在するかどうか調べる
のには,exists()メソッドが使用できる。
 getAbsolutePath()getAbsoluteFile()は,このFile型オブジェクトが表すファイル(ディ
レクトリ)の絶対パスまたは絶対パス名(File型オブジェクト)を返す。getCanonicalPath()
ソッドやgetCanonicalFile()メソッドは,このFile型オブジェクトが表すファイル(ディレクト
リ)の正規パス名または正規抽象パス名(File型オブジェクト)を返す。正規パス名とは,一意
のパス名で,シンボリックリンク(例えば,Windowsにおけるショートカット)などを解決し,
..や.といった特別なディレクトリを表す記号を実際のディレクトリに直した絶対パス名であ
る。
 getPath()メソッドは単にパス名を返し,getName()はファイル名(ディレクトリ名)を返す。
getParent()getParentFile()は,それぞれ親ディレクトリのパス名と抽象パス名を返す。
このFile型オブジェクトが指す抽象パス名が絶対パスかどうかを検査するのが,isAbsolute()
メソッドである。また,File型オブジェクトがディレクトリかどうかを検査をするのがisDirectory()
である。一方,isFile()はファイルかどうかを検査する。ファイル(ディレクトリ)が一般ユー
ザから隠されている特殊な「隠しファイル(ディレクトリ)」かどうかを検査するのには,isHidden()
メソッドが使える。
 ファイルの最終更新時刻を得るには,lastModified()が利用できる。返り値の時刻は,long型
で,グリニッジ標準時の1970年1月1日0時0分0秒からのミリ秒で表されている。最終更新
時間を明示的に設定するにはsetLastModified()を使う。ファイルの長さを得るには,length()
を利用できる。
 File型オブジェクトの指すディレクトリ内にあるファイル・ディレクトリの一覧を得るに
は,list()listFiles()が使用できる。この一覧では,条件にあったものだけをフィルタリン
グしてリストアップすることが可能である。
 フィルタリングを行うためには,インタフェイスFilenameFilterFileFilterを実装するクラ
スのオブジェクトを使う。FilenameFilterとFileFilterのabstractメソッドをTable 3,Table 4
に示す。これらのインタフェイスは,accept()というabstractメソッドを持っており,この
メソッドがtrueを返すファイルのみがリストアップされることになる。

 
 ファイルシステムのルートをリストアップするには,listRoots()を使う。UNIXではルート

  /
だけであるが,Windowsではドライブごとにルートがあるために,複数のルートが
  C:\
  D:\
というようにリストアップされる。
 ディレクトリを作成するには,mkdir()メソッドを使う。複数の必要な親ディレクトリも含
めて作成する場合は,mkdirs()を用いる。mkdirs()は失敗時であっても,親ディレクトリの
いくつかは作成されていることもありえるので,注意が必要である。
 ファイルやディレクトリの名前を変更するには,renameTo()を使う。また,ファイルやデ
ィレクトリを読み込み専用にするには,setReadOnly()を利用できる。
 抽象パス名を文字列パス名に変換するには,toString()を用いる。URIやURLに変換するには,
toURI()toURL()を使う。
 フィールドとしては,システム依存のパス名のディレクトリ階層の区切りに使う文字を表す
separatorCharseparatorがある。また,複数のパス名を併記するときに,パス名の間を区切
る記号もシステム依存で,このパス名の区切り文字はpathSeparatorCharpathSeparator
格納されている。

●Fileの使用例
 Fileの使用例をList 3に示す。
List 3 FileTest.java


List 3-<①>でまず必要になるストリーム変数を宣言しておく。
List 3-<2>ではファイルシステムのすべてのルートをリストアップして表示している。
 List 3-<3>では,UNIX流に言えば
  dir1/dir2/dir3
Windows流に言えば
  dir1\dir2\dir3
というディレクトリを作成している。List 3-<4>では,作成したdir3ディレクトリの下に一時
ファイルを2つ作成している。一時ファイルは,仮想マシンの終了時に自動的に削除されるよう
に設定されている。
 次に,作成した一時ファイルにいったん文字列を書き込み(List 3-<5>),書き込んだファイ
ルからまた文字列を読み込んで表示する(List 3-<6>)。
 List 3-<7>では,dir3ディレクトリ内のファイル・ディレクトリの一覧を表示している。
List 3-<8>はcatchブロック,List 3-<9>はfinallyブロックである。

●FileDescriptorクラス
 最後に,FileDescriptorクラスを紹介しよう。「ファイルディスクリプタ」と読む。FileDescriptor
は,ファイル記述子という,ファイル(ディレクトリ)のJavaシステム内での表現である。Table 5
にFileDescriptorのメソッドを示す。

 通常,ファイル記述子を使う用途は2つだけである。一度オープンしたFileInputStream,FileOutputStream,RandomAccessFileからはgetFD()メソッドによって
FileDescriptorオブジェクトを得ることができる。そして,ほとんどのファイルストリーム
(RandomAccessFileを含む)はFileDescriptorオブジェクトを引数に指定して生成するコンス
トラクタを持っている。そのため,あるファイルストリームと同じファイルを対象とした別の
ストリームをオープンするのに,FileDescriptorを使用できる
。これが第1の用途です。
 第2の用途は,バッファとファイルの厳密な同期である。一般に,出力ストリームに対して
はflush()メソッドを呼んでバッファ上にあるデータをファイルなどに書き込む。しかし,実際
にはプラットフォームのオペレーティングシステムでもバッファリングが行われていることが
多い。つまり,バッファリングは,オペレーティングシステムのレベルとJavaのレベルの2重
で行われている可能性がある。
 バッファリングがオペレーティングシステムでも行われている場合,Javaのレベルでflush()
メソッドを呼んでも,実際にはオペレーティングシステムの出力バッファに書き込まれるだけ
で,ファイル上には書き込まれていない可能性もあるわけである。
 そこで,確実にデバイス上のファイルにデータをフラッシュして,ファイル上のデータの内
容とバッファ上の内容を同期する手段が必要になる。この確実な同期を行うのがFileDescriptor
sync()メソッドなのである。
 よって,確実にバッファの内容をフラッシュしたい場合は,ファイルストリームのgetFD()
メソッド使ってFileDescriptor型オブジェクトを取得しておき,そのsync()メソッドを呼ぶよ
うにすると良い。ただし,フラッシュには時間がかかるので,頻繁なsync()メソッドの呼び出
しは実行パフォーマンスを低下させるので注意しよう。

【入出力処理のまとめ】
 入出力処理がある程度利用できるようになると,プログラミングの幅が広まる。ぜひ積極的
に使ってマスターして欲しい。

【次回は】
 次回は,スレッドについて解説する予定である。

戻る