Concatenate Streams

Revised: Mar./16th/2002

UNIX 系 OS では、テキストファイルの中味を見たり、複数のテキストファイルを一つにつなげて出力する為のコマンドが用意されています。このコマンドは cat コマンド (conCATenate) と呼ばれています。 Java でも、複数のストリームをつなげるクラスが用意されています。

SequenceInputStream は複数の入力ストリームから一つの入力ストリームを作ります。

継承階層:

java.lang.Object
  |
  +--java.io.InputStream
        |
        +--java.io.SequenceInputStream

コンストラクタ:

SequenceInputStream(Enumeration e)
SequenceInputStream(InputStream s1, InputStream s2)

二つの InputStream 型のオブジェクトを連結するのが基本的な使い方です。三つ以上のストリームの連結には java.util.Enumeration (列挙)インタフェースを使います。このインタフェースを実装 (implements) したクラス型のオブジェクトは、一連の要素を列挙として参照し、次のメソッドが使えるようになります:

java.util.Enumeration のメソッド
booleanhasMoreElements() 列挙にさらに要素があるかどうかを判定します。
ObjectnextElement() 列挙に 1 つ以上の要素が残っている場合は、次の要素を返します。

サンプル

ここでは UNIX 系 OS の cat コマンドの再現をしてみましょう。コマンドライン引数で与えられたパスの列挙を連結して標準出力に出力します。

  1. ファイルの列挙を扱う Enumeration の実装を作る
  2. このオブジェクトをコンストラクタ引数にしてインスタンス化して出力する。

Enumeration のサンプル

コンストラクタでは、文字列の配列で受け取った引数から、InputStream オブジェクトの列挙をつくり、インタフェースのメソッドを実装します。

このクラスが満たすべき条件は次のようにまとめられます。

Enumeration インタフェースの実装
SequenceInputStream クラスの要求

「実行時の型」とは、 nextElement() の戻り値のことですが、インタフェースでは、全てのクラスのスーパークラスである Object 型が指定されているので、任意のクラス型が許されます。また、 SequenceInputStream クラスの要求である InputStream クラスは全てのバイトストリームクラスのスーパークラスなので、任意のバイトストリームが許されます。このサンプルではファイルを扱うので、実際に作成するのは FileInputStream です。

まとめると、代入の自動型変換は次のようになっています。

[InputStream] = [FileInputStream]
[Object] = [InputStream]

FilesList.java:

import java.io.*;
import java.util.*;

class FilesList implements Enumeration {
	private String[] files;
	private int counter = 0;

	FilesList(String[] args) {
		files = args;
	}

	// 要素が残っていれば true
	public boolean hasMoreElements() {
		if (counter < files.length) {
			return true;
		} else {
			return false;
		}
	}

	// 次の要素を返す
	public Object nextElement() {
		InputStream in = null;

		// 次の要素が無ければ例外
		if (!hasMoreElements()) {
			throw new NoSuchElementException("ファイル終了!");
		} else {
			String retFile = files[counter];
			counter++;
			try {
				in = new FileInputStream(retFile);
			} catch (IOException e) {
				System.err.println(files[counter]
				                   + ": 処理できない!");
			}
		}

		// InputStream 型の参照を返す
		return in;
	}
}

コントロールクラス

つぎはこのクラス FilesList を利用する main() メソッドを作ってみます。引数に複数の文字列を受け取って、この配列の参照を上のクラスのコンストラクタ引数に与えます。

Cat.java:

import java.io.*;

class Cat {
	public static void main(String[] args) throws IOException {
		FilesList files = new FilesList(args);

		SequenceInputStream in = new SequenceInputStream(files);

		BufferedReader bin
		    = new BufferedReader(new InputStreamReader(in));
		String str="";

		while ((str = bin.readLine()) != null) {
			System.out.println(str);
		}

		bin.close();
		in.close();
	}
}

まず、上で作った FilesList クラスをインスタンス化し、これを引数として SequenceInputStream を作っています。

さらにこれを BufferedReader クラスでラップしています。 SequenceInputStream 型のストリームはバイトストリームなので、文字ストリームに変換する為に、 InputStreamReader を間に挟んでいます。

あとは readLine() で行を読み込んで出力しています。

実行結果:

C:\IO>javac FilesList.java

C:\IO>javac Cat.java

C:\IO>java Cat test.txt test2.txt test3.txt
Hello World.

Bye World.
こんにちは

さようなら
Buongiorno.

Arrivederci.

C:\IO>

test.txt

Hello World.

Bye World.

test2.txt

こんにちは

さようなら

test3.txt

Buongiorno.

Arrivederci.



Copyright © 2002 SUGAI, Manabu. All Rights Reserved.