プログラミング応用b 第7回『ファイル入出力の基礎』07-03. 『文字データ入力系ストリーム』 |
では,文字データ入出力系ストリームの解説をする。文字エンコーディング(文字コード)の変換例
も登場するので,しっかり勉強しよう。また,ファイルを配列の様に扱うランダムアクセスファイル
の基本も学習する。最後に,ファイルを扱う上で便利なクラス File と FileDescriptor について
も紹介する。
■3. 『文字データ入力系ストリーム』
入出力ストリームを表すクラスを文字通り「ストリーム」と呼ぶことがある。
Fig.1に,文字データ入力ストリームを表すクラス群の関係を示す。
これらのクラスは java.io パッケージ所属である。
では,それぞれのストリームクラスを簡単に紹介していこう。
■3.1. 『スーパークラスReader』
まず,すべての文字データ入力ストリームのスーパークラスとして,抽象クラス Reader が用意されて
いる。このため,文字データ入力ストリームのことを,単に“リーダ”と呼ぶこともある。Readerは,
文字データ入力ストリームが備えるべき基本的なメソッドを定義している抽象クラスである。
つまり,Fig.1 に示した Readerの全サブクラス(リーダ)はこれらのメソッドを持っている。
抽象クラス Reader のメソッドを Table 1 に示す。重要なメソッドとしては,使い終わったストリーム
をクローズする close メソッド,ストリームから文字を読み込む複数の readメソッド がある。
表下部に注記してあるように,これらReaderのメソッドはエラーが起こって本来の目的が達成できな
かった場合は,IOException型例外を投げることにも注意しよう。
その他,入力データを指定した数だけ読み飛ばす skip メソッドもある。
また,入力ストリームによっては,マーク機能を持つものもある。
コラム
マーク機能とは,ストリームからデータを入力しているとき,マークを設定すると,後でそのマーク位置 まで戻って再びデータを再入力できるようにする機能である。mark( )メソッドを呼び出すと,現在の読み込み 位置にマークが設定される。そして,reset( )メソッドを呼び出すと,最後に設定したマーク位置まで読み込み位置 が戻され,そこから再度読み込みが開始される。 実際には,マークを設定した時点からreset( )が呼び出されるまでに入力したデータをストリームオブジェクトが 保存しておくことで,このマーク機能は実現されている。最大どれだけのデータを憶えておくようにするか指定で きるmark( )メソッドも用意されている。すべてのストリームでマーク機能が使えるわけではなく,マーク機能を 持っているストリームでは,markSupported( )メソッドがtrueを返すようになっている。 |
■3.2. 『基本ストリームとラッパストリーム』
では,Fig. 1 にもどって,今度は Reader のサブクラスを見てみよう。Fig.1 を再掲する(下図をクリック
すると授業で扱わないクラスは非表示になる)。
Reader のサブクラスは,2つのグループわけられる。ひとつは,本当の意味でのストリームで,
文字データの入力源(キーボード・通信路・ファイルなどのハードウェア装置,文字列・文字配列
などの文字を格納しているデータ構造)と直接接続するストリームである。ここでは便宜的に
“基本ストリーム”と呼ぶことにする(下図)。
もうひとつのグループは,他のストリームの後段に珠々繋ぎのように接続されて,ストリームに
付加機能をつけるためのストリームで,ここで便宜的に"ラッパストリーム(wrapper stream)"と呼ぶ
ことにする。
単独のラッパストリームオブジェクトの特徴を挙げると下図の様になる。
このように,ラッパストリームは Reader型参照値で入力元のリーダ(基本ストリームか他のラッパストリーム)
を珠々繋ぎにできる(下図)。
ラッパストリームを使ってリーダを珠々繋ぎにする,という仕組みの応用例を下図に示す。文章が多いように
見えるが,独習のために細かく書いてあるだけで授業での解説を聞けばアイディアはそれほど複雑ではない
ことが分かるだろう。
以上の,ラッパストリームに関する図(1)〜(4)をひとつにまとめた図はこちら。
ここで,入力ストリームオブジェクト(リーダ)を初期化し,そのストリームをオープンする
コンストラクタの引数について以下の表にまとめておく。
文字入力ストリーム(リーダ)の種類 | コンストラクタへの引数 |
基本ストリーム | 入力源を指定する情報 (例:ファイルを入力源とするFileReaderクラス の場合,ファイル名などファイルを指定する情報) |
ラッパーストリーム | 入力元となるストリームオブジェクトへのReader型参照値。 |
■3.3. 『文字入力用の基本ストリーム』
では,文字入力用の基本ストリームから見ていこう。文字入力用の基本ストリームには,Fig.1(再掲)のように,
4つのストリームが用意されている。授業では,このうち InputStreamReader と FileReader のみ扱う。
■ InputStreamReader
InputStreamReader は,ファイルを入力源とする基本ストリーム(リーダ)である。下図に InputStreamReader型リーダ
オブジェクトの「ファイルを入力源として直接接続した」概念的な姿とその真の姿を示す。InputStreamReader型リーダは
基本ストリームのはずなのに,ラッパストリームとして実現されているのは,文字データには,文字コード(文字エンコーディング)
の違いという問題があるためである。
このように,InputStreamReaderの実際の働きはラッパストリーム(直接接続されるデータ入力元が入力源ではない)なのだが,
文字データストリームの事実上の基本ストリームとして扱ってかまわない。
InputStreamReaderの主なコンストラクタと主なメソッドを下表に示す。
InputStreamReader の主なコンストラクタ ※実態はラッパストリームなので,(InputStream型の)入力元ストリームオブジェクトへの参照値を(第1)実引数に取る。 ※第2実引数として,第1実引数で指定された入力元ストリームの仮定される文字コードを指定する情報を取る。 これらの実引数によって初期化されストリームをオープンする。 |
|
InputStreamReader( InputStream in, String charsetName ) InputStreamReader( InputStream in ) |
InputStream型ストリーム in を入力元とするストリームを生成・初期化する。 |
InputStreamReader の主なメソッド |
|
■FileInputStream
なお,( InputStreamReaderの入力源である) InputStream には,接続する実際の入力源ごとにサブクラスが用意されている。
ここで,バイトデータ入力系ストリームのうち,今回話しに登場するクラスの継承関係の図を以下に示す(図をクリックすると
イトデータ入力系ストリームのすべてのクラスを表示する)。これらのクラスは java.io パッケージ所属である。
下図でもわかるとおり,ファイルをデータ入力源とするバイトデータ入力系の基本ストリームとしては,FileInputStream が
定義されているので,ファイルから文字を読み込むときにはこれをInputStreamReaderの入力元として利用する。
FileInputStream のコンストラクタを下表に示す。
FileInputStream のコンストラクタ ※基本ストリームなので入力源(ここではファイル)を示す情報をコンストラクタの実引数として渡してストリームをオープンする。 |
|
FileInputStream ( String name ) FileInputStream ( File file ) FileInputStream ( FileDescriptor fd ) |
これらのコンストラクタは, |
■ FileReader
FileReader は,InputStreamReader のサブクラスで,
・入力源をファイルとする (つまり,指定されたファイルを入力源とするFileInputStream型オブジェクトを自動的にオープンして入力元とする)
・入力源の文字エンコーディングをデフォルトエンコーディングであると仮定して入力を行う。
というクラスで,簡易的にファイルから文字入力できる。
ただし,FileReader は上記の様に入力源の文字エンコーディングをデフォルトエンコーディングであると仮定しているので,(デフォルトエン
コーディングの設定を変更しない限り)デフォルトエンコーディングで指定された文字コードで書かれたテキストファイルしか読めないので注意
しよう。
FileReader のコンストラクタを Table 2-(3) および以下の表に示す。
FileReader のコンストラクタ | |
FileReader( String name ) FileReader( File file ) FileReader( FileDescriptor fd ) |
これらのコンストラクタは, ・String型のファイルパス名文字列 name ・File型オブジェクト file ・FileDescriptor(ファイル記述子)型オブジェクト fd で指定されたファイルを入力源とする文字入力の基本ストリームをオープンする。実際には上図(2)のように指定されたファイルを入力源としたFileInputStream 型基本ストリームをオープンし,それをラップするFileReader型オブジェクトを生成・初期化する。 指定されたファイルが存在しなかったり,ディレクトリだったりなどの理由でオープンできないときは,FileNotFoundException 例外を投げる。 ※File型とFileDescriptor型については後で紹介する。 |
FileReader の主なメソッド |
|
以下に,FileReader を利用した簡単なデフォルトエンコーディングによるテキストファイル読み込みの例を示す。
テキストファイル「AandB.txt」をダウンロードして,Eclipse で作成したプロジェクトフォルダの中に入れて実行してみよ。
本授業のインストールガイドでEclipseをインストールしていれば,デフォルトエンコーディングはおそらく UTF-8
である。そして,「AandB.txt」は UTF-8 で「AとBと」書かれているので,正しく読めるはずである。
|
|
|
■3.4. 『文字入力用のラッパストリーム』
Fig.1 に示すように,文字入力用のラッパストリームには,
・InputStream のラッパストリーム
・Reader のラッパストリーム
の2系統がある。前者に関しては,基本ストリームの一部として紹介済みなので,後者のうち,授業で使用する
BufferedReader だけ紹介する。
■BufferedReader
BufferedReader は,元の入力ストリームにバッファ機能を付加するラッパストリームである。
ディスク装置などの入出力機器のデータアクセス速度は,メモリ上のデータへのアクセス速度にくらべ,
圧倒的に遅い。そのため,少量のデータを頻繁に読み込むなどすると,非常に時間がかかってしまう(下図(1))。
そこで,バッファ(buffer, 緩衝地帯という意味)と呼ばれるメモリ上の領域に,まとまった量のデータ
をあらかじめ一気に読み込んでおき, 実際に読み込むときには,そのバッファからデータを入力する
ようにすれば,入力処理を高速化することができる(上図(2))。
BufferedReader オブジェクトは,バッファ領域を確保して,バッファ機能を提供してくれるラッパストリーム
なのである。積極的に使うべきラッパストリームと言える。主なコンストラクタとメソッドを下表とTable 2-(7)
に示す。特徴的なのは,非常に便利な行単位の入力を行うメソッド readLine を持つことである(下表)。
BufferedReader のコンストラクタ | |
BufferedReader( Reader in ) BufferedReader(Reader in, int sz) |
Reader型ストリームオブジェクト in をラップするようにするコンストラクタ。バッファサイズを第2引数szで指定することもできる。 |
BufferedReader のメソッド |
|
String readLine( ) |
1行分の文字列(行末含まず)を読んで返す。改行文字(\n),復帰文字(\r), または連続した改行復帰の2文字(\n\r)を行区切りとして認識する。 入力ストリームの終わりに達している場合は null を返す。 ※入力元からバッファへ一度に多くの文字を読み込むという特徴を活かして 行単位で文字列を返す機能を実現している。 ※入出力エラーが発生した場合は,IOException例外を投げる。 |
■3.5. 『テキストファイルの読み出し例』
では,実際にテキストファイルから文字データを読み込んでみよう。
まず,Eclipseで Javaプロジェクトを新規作成する。次に読み込む対象のファイル AandB.txt をダウンロードして,
Eclipseのパッケージエクスプローラー内の作成したプロジェクトフォルダ内にドラッグ&ドロップしてコピーする。
プロジェクトフォルダの『src』フォルダと並ぶ形で登録できればOK。
作成したJavaプロジェクトに以下のソースプログラム TextReading.java を作成して実行してみよ。
ソースプログラム:TextReading.java
※まず,入出力ストリームの機能はほとんどエラーが起こったときに例外を投げるので,例外の枠組みを使って記述することに注意しよう。
①:java.io パッケージをインポートする。
②:mainメソッドに IOException例外を投げることを明示する例外指定を書いている。理由は後述する。
③:tryブロックの前で,使用するストリームオブジェクトを参照するための変数をnull を初期値として宣言しておく。この例では,
FileInputStream, InputStreamReader, BufferedReader
を使用する。
④:まず,入力源であるテキストファイルと接続するFileInputStream型の基本ストリームをオープンする。入力源の情報としてコンストラクタ
への実引数としてテキストファイル名を渡していることに注意。以下の様な状態になる。
⑤:入力元として④でオープンしたFileInputStream型ストリームへの参照値 fis と,文字エンコーディング"UTF8"を,コンストラクタの
実引数として渡し,InputStreamReader型ストリームをオープンしている。以下の様な状態になる。
⑥:入力元として⑤でオープンしたInputStreamReader型ストリームへの参照値 isr をコンストラクタの実引数として指定し,BufferedReader
型ストリームをオープンする。以下の様な状態になる。これで,テキストファイルから文字データを行単位で読み込む準備が整った,。
⑦:BufferedReader型ストリーム br の readLineメソッドを呼び出して1行分読み込んで表示する。"AとBと"と表示されるはずである。
⑧〜⑨:発生する可能性のある例外をキャッチしてエラーへの対処を行う。ここでは単にキャッチした例外オブジェクトを表示しているだけ。
⑩:tryブロックの中の処理で例外が発生する・しないに関係無く,finallyブロック内で使い終わったすべてのストリームを入力源から遠い
ラッパストリームから順にクローズしていく。
一番入力源の遠いラッパストリーム BufferedReader型ストリーム br のcloseメソッドだけ呼べばいいと
思うかもしれないが,もし
BufferedReader型オブジェクトをオープンしようとすところでエラーが起こった場合,
if( br != null ) br.close( );
だけでは,
FileInputStream型ストリームと InputStreamReader型ストリームはクローズされないことになってしまう。
※③で,tryブロックの前でストリーム参照用の変数を宣言したのは,finallyブロックでこれらの変数を使用するため。
※②で,mainメソッドに IOException例外の例外指定をしたのは,finallyブロック内で呼び出す close メソッドが IOException例外を投げる
可能性があるため。このままでは,
finallyブロック内で呼び出す close メソッドで例外が発生した場合は,mainメソッドすなわちプログラム
自体が強制終了するため,それを防ぎたい場合は,try-catch-finallyブロックをさらに tryブロックで囲み,この外部のtryブロックで
IOException例外をキャッチするようにしよう。
なお,この例で,ファイル内容をすべて読み込んで表示する場合,
・BufferedReaderのreadLineメソッドは,入力ストリームの終わりに達して読み込めなかった場合は null を返す
という性質を利用して,⑦の部分を
|
|
標準入力ストリーム System.in (オープン済みのInputStream型ストリームで,プログラム動作中にファイルやキーボード入力
などの入力源を切り替えることができる。通常はキーボード入力に接続されている)を入力源として
,InputStreamReader型基本ストリーム
|
|
|
と書ける。
(2) キーボードからの文字列入力の簡易的な方法
java.util パッケージの Scanner クラスを使用する。Scannerはストリームではないが,
・コンストラクタの実引数として入力源を指定してオープンする。
・使用し終わったら close メソッドを読んでクローズする必要がある。
という点では,文字データ入力の基本ストリームに似ている部分が有る。
Scannerを使うと,
ファイル・ImputStream型ストリーム(キーボード入力(標準入力)含む)・String型文字列
などを入力源として文字列を読み込み,文字列や数値などを読み込むことができる。
非常に高機能なので,今回は1行分の文字列を読み込む例だけを示す。実行の様子は(1)と
同じである。
|