IOストリーム関連のメモ。自分がよく使うクラスに絞って、使い分けやコツなどをまとめてみた。
入出力の単位:
自分なりのイメージとしては、バイト単位の入出力機能がまずベースとしてあり、その上に文字符号化方式と組み合わせることで文字単位の入出力機能が存在すると考えると分かりやすい。
参考資料:
java.io.InputStream が抽象クラスとして提供されている。これをインターフェイスとして、各種実装クラスが派生している。
java.io.InputStream --> java.io.FilterInputStream : フィルタ処理向けラッパークラス --> java.io.BufferedInputStream : 内部バッファによる効率化を図る --> java.io.DataInputStream : プリミティブ型を読むときに使う --> java.io.FileInputStream : ファイルから読むときに使う --> java.io.ByteArrayInputStream : 既存のbyte配列を入力ソースにする
FilterInputStreamはコンストラクタで渡されたInputStreamのインスタンスを入力ソースに使う。
例えばファイルを入力ソースとして、内部バッファによる効率化を図るときは次のように組み合わせる。
new BufferedInputStream(new FileInputStream("foo/bar.dat"));
java.io.OutputStream が抽象クラスとして提供されている。これをインターフェイスとして、各種実装クラスが派生している。
java.io.OutputStream --> java.io.FilterOutputStream : フィルタ処理向けラッパークラス --> java.io.BufferedOutputStream : 内部バッファによる効率化を図る --> java.io.DataOutputStream : プリミティブ型を書くときに使う --> java.io.FileOutputStream : ファイルに書くときに使う --> java.io.ByteArrayOutputStream : 自動拡張するbyte配列を出力先にする
FilterOutputStreamはコンストラクタで渡されたOutputStreamのインスタンスを出力先に使う。
例えばファイルを出力先として、内部バッファによる効率化を図るときは次のように組み合わせる。
new BufferedOutputStream(new FileOutputStream("foo/bar.dat"));
多くの参考書やWeb資料ではBufferedInput/OutputStreamと組み合わせているが、原理的には直接FileInput/OutputStreamを使っても問題ない。
java.io.Reader が抽象クラスとして提供されている。これをインターフェイスとして、各種実装クラスが派生している。
java.io.Reader --> java.io.BufferedReader : 他のReaderインスタンスをラップし、内部バッファによる効率化を図る --> java.io.InputStreamReader : InputStreamインスタンスをラップする --> java.io.FileReader : テキストファイル読み込み用の簡易クラス
ポイントとなるのがInputStreamReaderで、これによりバイト単位のInputStreamを文字単位のReaderに変換できる。コンストラクタに文字符号化方式を指定することができる。
例: /* システムのデフォルトの文字符号化方式を使う */ InputStreamReader(InputStream in); /* 文字符号化方式をjava.nio.charset.Charsetで指定する */ InputStreamReader(InputStream in, java.nio.charset.Charset cs);
実用上はInputStreamReaderをBufferedReaderでラップし、BufferedReaderのreadLine()により文字列(String)単位でテキストを読み込む使い方が多い。
new BufferedReader(new InputStreamReader(new FileInputStream("foo/bar.txt")))
個人的には、BufferedInputStreamまで挟むのはやり過ぎだと思う。
/* 多分やり過ぎ */ new BufferedReader( new InputStreamReader( new BufferedInputStream( new FileInputStream("foo/bar.txt") ) ) )
readLine()はBufferedReader独自のメソッド。readLine()を使いたい場合、コンストラクタを受けるインスタンスの型は、インターフェイスであるReaderではなく実装クラスであるBufferedReaderにする必要がある。
Reader r = new BufferedReader(...); String line = r.readLine(); /* コンパイルエラー : ReaderクラスにはreadLine()が未定義 */ → BufferedReader br = new BufferedReader(...); String line = br.readLine();
java.io.Writer が抽象クラスとして提供されている。これをインターフェイスとして、各種実装クラスが派生している。
java.io.Writer --> java.io.BufferedWriter : 他のWriterインスタンスをラップし、内部バッファによる効率化を図る --> java.io.OutputStreamWriter : OutputStreamインスタンスをラップする --> java.io.FileWriter : テキストファイル書き込み用の簡易クラス
ポイントとなるのがOutputStreamWriterで、これによりバイト単位のOutputStreamを文字単位のWriterに変換できる。コンストラクタによる文字符号化方式などはInputStreamReaderと同様。
実用上はOutputStreamWriterをBufferedWriterでラップし、さらに後述のPrintWriterでラップする使い方が多い。
JavaDocのサンプルを少し改変:
PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream("foo/bar.txt") ) ) );
readLine()と同様、PrintWriter独自の便利メソッドを使うため、コンストラクタを受けるインスタンスの型は、インターフェイスであるWriterではなく実装クラスであるPrintWriterにしている。
Reader/Writerが使われるのは大抵、外部データとの入出力処理である。
そのため、殆どのケースでInputStreamReader/OutputStreamWriterによる「バイト単位<>文字単位」変換処理が必要となる。
ファイルからの入出力を扱う場合は、FileReader/FileWriterがInputStreamReader/OutputStreamWriter不要の簡易クラスとして提供されている。ただし文字符号化方式がシステムのデフォルトに固定されるため、それ以外の文字符号化方式を使ったテキストファイルを使う場面には向いていない。
バイト単位と同様、原理的にはBufferedReader/Writerを挟まなくても使える。ただし、read/writeするたびにバイト<>文字変換が発生するため、効率が悪い。BufferedReader/BufferedWriterを使えば内部バッファでまとめて変換してくれるので効率的。特に理由がなければ、BufferedReader/BufferedWriterを組み合わせることになる。
プリミティブ型やインスタンスを文字列に変換して出力してくれる便利クラス:
java.io.PrintWriter : 任意の文字符号化方式と組み合わせられる java.io.PrintStream : システムのデフォルトの文字符号化方式に固定される
print()が改行なし、println()が改行付き。テキスト出力に便利で、前述のようにBufferedWriterをラップして使ったりする。
java.io.StreamTokenizerのJavaDocを参照。
標準入出力:
System.in : java.io.InputStream System.out : java.io.PrintStream System.err : java.io.PrintStream
外部プロセスの起動:
起動した外部プロセスの標準入出力との対話:
JDK1.5以上の場合、環境変数をKey/ValueのMapで指定できるなど、使いやすくラップしてくれるProcessBuilderクラスが利用出来る。
バイト単位・文字単位・文字列単位の入出力サンプルと、FilterInput/OutputStreamを使った16進ダンプのサンプル。
Foo.java:
import java.io.*; import java.nio.*; public class Foo { // バイト(byte)単位の入出力例 static void byte_io() throws Exception { byte[] data = new byte[10]; int len = 0; InputStream is = new BufferedInputStream( new FileInputStream("test.dat")); OutputStream os = new BufferedOutputStream( new FileOutputStream("test2.dat")); // サンプルなので、コピーするだけ。 while (-1 != (len = is.read(data))) { os.write(data, 0, len); } is.close(); os.close(); } // 1文字(char)単位の入出力例 static void char_io() throws Exception { char[] data = new char[10]; int len = 0; Reader r = new BufferedReader( new InputStreamReader( new FileInputStream("test.txt"), "Windows-31J" )); Writer w = new BufferedWriter( new OutputStreamWriter( new FileOutputStream("test2.txt"), "UTF-8" )); // サンプルなので、コピーするだけ。 while (-1 != (len = r.read(data))) { w.write(data, 0, len); } r.close(); w.close(); } // 文字列(String)単位の入出力例 static void line_text_io() throws Exception { BufferedReader r = new BufferedReader( new InputStreamReader( new FileInputStream("line.txt"), "Windows-31J" )); PrintWriter w = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream("line2.txt"), "UTF-8" ))); // サンプルなので、コピーするだけ。 String line; while (null != (line = r.readLine())) { w.println(line); } r.close(); w.close(); } public static void main(String args[]) throws Exception { byte_io(); char_io(); line_text_io(); } }
実行結果は省略。
FilterInput/OutputStreamの派生クラスで、read()/write()に渡されたデータを16進ダンプする。
FileInput/OutputStreamをラップすることで、ファイルから読み込んだ直後と、書きこむ直前のバイナリデータを確認できる。今回はFilterInput/OutputStreamからの派生にしたが、BufferedInput/OutputStreamからの派生でもよいだろう。
ただし、このサンプルはラップが多段になり「やり過ぎ」とも言える。効率面で問題になる可能性がある。実際に使うのであれば、デバッグ用であることを明示するか、直接FileInput/OutputStreamから派生させてラップ段数を節約するなど工夫が必要。
Bar.java:
import java.io.*; import java.nio.*; // InputStreamのread()にインターセプトして16進ダンプ class HexDumpFilterInputStream extends FilterInputStream { public HexDumpFilterInputStream(InputStream in) { super(in); } @Override public int read() throws IOException { int r = super.read(); System.out.format("read1() : 0x%02X\n", r); return r; } @Override public int read(byte[] b) throws IOException { int r = super.read(b); System.out.print("read2() : "); for (byte d : b) { System.out.format("0x%02X ", d); } System.out.print("\n"); return r; } @Override public int read(byte[] b, int off, int len) throws IOException { int r = super.read(b, off, len); int e = off + r; System.out.print("read3() : "); for (int i = off; i < e; i++) { System.out.format("0x%02X ", b[i]); } System.out.print("\n"); return r; } } // OutputStreamのwrite()にインターセプトして16進ダンプ class HexDumpFilterOutputStream extends FilterOutputStream { public HexDumpFilterOutputStream(OutputStream out) { super(out); } @Override public void write(int b) throws IOException { System.out.format("write1() : 0x%02X\n", (byte)b); super.write(b); } @Override public void write(byte[] b) throws IOException { System.out.print("write2() : "); for (byte d : b) { System.out.format("0x%02X ", d); } System.out.print("\n"); super.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { int e = off + len; System.out.print("write3() : "); for (int i = off; i < e; i++) { System.out.format("0x%02X ", b[i]); } System.out.print("\n"); super.write(b, off, len); } } public class Bar { public static void main(String args[]) throws Exception { BufferedReader r = new BufferedReader( new InputStreamReader( new HexDumpFilterInputStream( new FileInputStream("line.txt") ), "Windows-31J" )); PrintWriter w = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new HexDumpFilterOutputStream( new FileOutputStream("line2.txt") ), "UTF-8" ))); String line; while (null != (line = r.readLine())) { w.println(line); } r.close(); w.close(); } }
実行結果は省略。