WisdomSoft - for your serial experiences.

10.3 ストリームの読み込み

入力ストリームからデータを読み込む方法を紹介します。テキストファイルを開き、書き込まれている文字列を読み取って画面に表示します。

10.3.1 入力ストリーム

装置からデータを入力するには、出力ストリームとまったく同じ概念で入力ストリームを用います。しかし、入力ストリームはデータ型の変換方法が複雑であるという点で、出力ストリームよりも厄介です。入力対象は、最終的にバイト配列を入力しますが、これを文字列や数値に変換するのはプログラムの仕事となります。また、出力と異なり入力にはデータの末尾が存在します。

入力ストリームは java.io.InputStream クラスをスーパークラスとします。このクラスの設計は OutputStream と同じです。具体的な入力対象はサブクラスで決定されます。入力ストリームの対象は、キーボードやファイルかもしれませんし、ネットワークや録音装置かもしれないのです。対象が何であれ、最終的には InputStream 型として一貫した操作で入力データを受け取ることができます(表1)。

表1 InputStreamクラス
メソッド 解説
int available() この入力ストリームのメソッドの次の呼び出し側からブロックされることなく、この入力ストリームから読み込むことができる (またはスキップできる) バイト数を返します。
void close() この入力ストリームを閉じて、そのストリームに関連するすべてのシステムリソースを解放します。
void mark(int readlimit) 現在位置にマークを設定します。
boolean markSupported() mark および reset メソッドをサポートしているかどうかを判定します。
abstract int read() 次のバイトデータを読み込みます。
int read(byte[] b) データを読み込んでバイト配列 b に格納し、読み込んだバイト数を返します。
int read(byte[] b, int off, int len) off から len バイトまでのデータを読み込んでバイト配列 b に格納し、読み込んだバイト数を返します。
void reset() このストリームの位置を、入力ストリームで最後に mark メソッドが呼び出されたときのマーク位置に再設定します。
long skip(long n) 入力ストリームからのデータを n バイトだけスキップしてその範囲のデータを破棄します。

InputStream クラスの read() メソッドを用いて対象からデータを受けることができます。入力ストリームは、入力データがバイト配列として考えるため、入力位置というものが存在します。入力ストリームは、入力データの現在位置からバイトデータを読み込むのです。

引数を指定しない read() メソッドは戻り値としてバイトデータを返します。戻り値型は int となっていますが、これはバイトデータをワイドニング変換しただけです。read() メソッドを呼び出すたびに入力位置が自動的に進みます。そのため、read() メソッドを何度も呼び出せばいつかストリームの最後にたどり着きます。ストリームの終わりに達した場合は -1 を返します。

byte 型の配列を引数に指定する read() メソッドは、渡した配列の先頭から入力データを代入してくれます。配列には、入力データを格納できる十分なサイズを与える必要があります。

System クラスの in フィールドには、標準入力のストリームが格納されています。標準入力がどの装置なのかはシステムの状態に依存しますが、一般的にはキーボードなどの文字入力装置だと考えられます。このフィールドを使って、システムが定める標準入力からデータを取得することができます。

コード1
class Test {
	public static void main(String args[]) throws Exception {
		byte data[] =  new byte[0xFF];
		System.out.print("入力してください>");
		int len = System.in.read(data);
		System.out.print("入力した文字 = " + new String(data , 0 , len));
	}
}
実行結果
>java Test
入力してください>Kitty on your lap
入力した文字 = Kitty on your lap

コード1は、標準入力からデータを入力し、それを再表示するエコー・プログラムです。一般的には、標準入力はキーボードに設定されているため、これでユーザーに入力を促すことができるようになります。プログラムは System.in フィールドの入力ストリームから read() メソッドを用いてバイト配列を取得し、これを文字列に変換して表示しています。

配列のサイズは 0xFF すなわち 255 までのバイト配列なので、ASCII 文字の場合 255 文字までしか入力することができません。入力されたバイト数が 255 以下の可能性があるため、入力されたバイト数を read() メソッドの戻り値から取得して、入力されたバイト数だけ文字列に変換するように仕組んでいます。

入力ストリームのインスタンスを生成するには InputStream 抽象クラスを継承するサブクラスを用います。標準ライブラリでは、ディスクに保存されているファイルを読み込む java.io.FileInputStream クラス、メモリ上のデータを読み込む java.io.ByteArrayInputStream クラス、オブジェクトを読み込む java.io.ObjectInputStream、オーディオデータを読み込む javax.sound.sampled.AudioInputStream クラスなど、様々なクラスが提供されています。

例えば、FileInputStream クラスを利用することで、ディスクに格納されているファイルからデータを読み込むことができます(表2)。

表2 FileInputStreamクラス
コンストラクタ 解説
FileInputStream(File file) ファイルシステムの実際のファイル (File オブジェクト file により指定) に接続することにより、FileInputStream を作成します。
FileInputStream(FileDescriptor fdObj) ファイルシステムの実際のファイルへの既存の接続を表すファイル記述子 fdObj を使用して、FileInputStream を作成します。
FileInputStream(String name) ファイルシステムの実際のファイル (パス名 name により指定) に接続することにより、FileInputStream を作成します。
メソッド
protected void finalize() ファイル入力ストリームへの参照が存在しなくなったときに、このストリームの close メソッドが確実に呼び出されるようにします。
FileChannel getChannel() このファイル入力ストリームに関連付けられた、一意の FileChannel オブジェクトを返します。
FileDescriptor getFD() この FileInputStream に使用されているファイルシステムの実際のファイルへの接続を表す FileDescriptor オブジェクトを返します。
コード2
import java.io.*;

class Test {
	public static void main(String args[]) throws Exception {
		InputStream in = new FileInputStream(args[0]);

		while(true) {
			byte data[] =  { (byte)in.read() };
			if (data[0] == -1) break;
			else System.out.print(new String(data));
		}
		in.close();
	}
}
実行結果
>java Test test.txt
This is content in test.txt file.
Must not write except for ASCII code.

コード2を実行するには、コマンドライン引数からテキストファイルのパスを指定してください。プログラムは指定されたテキストファイルを開き、入力ストリームからデータを読み込みます。データを読み込む方法は while() ループを利用して、バイト単位でテキストを読み込み、文字列に変換しています。ストリームの終端に到達すると read() メソッドが -1 を返すため、このタイミングでループを終了させています。

バイト単位で文字列を生成しているため、このプログラムは ASCII コード以外をサポートすることはできません。

10.3.2 Readerクラス

入力ストリームから文字列を取得することをサポートする java.io.Reader クラスを使うと便利です。Reader 抽象クラスは入力ストリームからデータを読み込むすべてのクラスのスーパークラスとなります。これは出力時の Writer クラスと同じ概念です。Reader クラスは、入力ストリームから文字配列を読み込む read() メソッドを提供しています(表3)。

表3 Readerクラス
コンストラクタ 解説
protected Reader() そのクリティカルなセクションがリーダ自体で同期する、新しい文字ストリームリーダを作成します。
protected Reader(Object lock) そのクリティカルなセクションが指定されたオブジェクトで同期する、新しい文字ストリームリーダを作成します。
メソッド
abstract void close() ストリームを閉じます。
void mark(int readAheadLimit) ストリームの現在の位置にマークを設定します。
boolean markSupported() このストリームが mark() オペレーションをサポートするかどうかを判定します。
int read() 単一文字を読み込みます。
int read(char[] cbuf) 配列に文字を読み込みます。
abstract int read(char[] cbuf, int off, int len) off から len 文字だけ配列の一部に文字を読み込みます。
boolean ready() このストリームが読み込み可能かどうかを判定します。
void reset() ストリームをリセットします。
long skip(long n) n で指定した数だけ文字をスキップします。

入力ストリームから文字列を読み込む場合、Java 特有の問題が発生します。どこでも実行可能であることを想定して開発しなければならない Java プログラムにとって、プラットフォームからの入力は文字コードの変換が必要になるのです。通常は、デフォルトの文字エンコーディングが設定されているため、初心者が特別意識する必要はありません。

バイトデータと文字データの橋渡しの役割を持つ Reader オブジェクトを生成するには java.io.InputStreamReader クラスを使います。このクラスは Reader クラスを実装しているため、入力ストリームからデータを読み込み、文字列に変換する基本的な手段となるでしょう(表4)。Java で作られたソフトウェアを国際化する方法や、文字コードの詳細を知りたい場合は「JavaTM2 SDK, Standard Edition ドキュメント」の「国際化」を参照してください。

表4 InputStreamReaderクラス
コンストラクタ 解説
InputStreamReader(InputStream in) デフォルトの文字エンコーディングを使う InputStreamReader を作成します。
InputStreamReader(InputStream in, Charset cs) 与えられた文字エンコーディングを使う InputStreamReader を作成します。
InputStreamReader(InputStream in, CharsetDecoder dec) 与えられた文字エンコーディングデコーダを使う InputStreamReader を作成します。
InputStreamReader(InputStream in, String charsetName) 指定された文字エンコーディングを使う InputStreamReader を作成します。
メソッド
String getEncoding() このストリームで使用される文字エンコーディングの名前を返します。

InputStreamReader クラスを利用することで、入力ストリームからデータを読み込む Reader オブジェクトを生成することができます。しかし、read() メソッドを用いて文字を読み込むことができるものの、配列を用意したり、1 文字ずつ読み込むのはなんだか面倒ですね。ある程度、まとめて文字列を生成したいと思うことでしょう。ならば、java.io.BufferedReader クラスを利用します。

表5 BufferedReaderクラス
コンストラクタ 解説
BufferedReader(Reader in) デフォルトサイズのバッファでバッファリングされた、文字型入力ストリームを作成します。
BufferedReader(Reader in, int sz) 指定されたサイズのバッファでバッファリングされた、文字型入力ストリームを作成します。
メソッド
String readLine() 1 行のテキストを読み込みます。

BufferedReader クラスは、行ごとに文字列を読み込む機能を提供しています。このクラスは、read() メソッドを呼び出す以前に入力ストリームから文字列を読み込み、バッファリングします。効率的に文字列を読み込むことができるため、テキストファイルなどから文字列を取得するプログラムなどに使うことができるでしょう。

コード3
import java.io.*;

class Test {
	public static void main(String args[]) throws Exception {
		InputStream in = new FileInputStream(args[0]);
		BufferedReader reader =
			new BufferedReader(new InputStreamReader(in));

		while(reader.ready()) System.out.println(reader.readLine());
		
		reader.close();
		in.close();
	}
}
実行結果
>java Test test.txt
test.txt テキストファイルの内容です
このファイルは Test.class と同じディレクトリに保存してください。

コード3は、日本語の入力にも対応したプログラムです。コマンドライン引数で指定したテキストファイルを標準出力に出力します。プログラムは、InputStream オブジェクトを生成し、そこから InputStreamReader オブジェクトを生成しています。これを、BufferedReader クラスのコンストラクタに渡して、この Reader オブジェクトから文字列を取得する BufferedReader オブジェクトを生成しています。

その後、プログラムは ready() が false を返すまで、繰り返し処理で行ごとに文字列を出力しています。このように、Reader クラスのサブクラスを利用することで、日本語のテキストデータを簡単に取得することができるのです。