#navi_header|Java| IOストリーム関連のメモ。自分がよく使うクラスに絞って、使い分けやコツなどをまとめてみた。 #more|| #outline|| ---- * 基本的な考え方と参考資料 - 「ストリーム」 : read/writeで一方通行が基本。read系はひたすら前進して読み込んでいく。write系はひたすら書きこんでいくだけ。 - java.ioやjava.nioにまとめられている。基本的にランダムアクセスは無し。 - ランダムアクセスしたい場合(=seek()を使いたい場合)はjava.io.RandomAccessFileクラスを使う。 - 入出力先でファイルを指定する場合は java.io.File を使える。java.io.Fileを使わず、直接ファイル名で指定できる場合もある。 入出力の単位: - バイト(byte)単位:InputStream/OutpuptStream系 - 文字(char)単位:Reader/Write系 -- InputStream/OutputStream系のインスタンスをコンストラクタに渡す場合が多い。 -- 文字符号化方式(encoding schema)を指定できる場合が多い。 -- 出力処理のイメージとしては、文字(char)を文字符号化方式でバイトデータに変換し、コンストラクタで渡されたOutputStream系に渡すイメージ。入力処理はその逆。 --- Javaは文字をUnicodeでcharやString型に保存するので、外部との入出力でバイトデータと変換するときは文字符号化方式の指定が必要。 --- 文字符号化方式の指定が不要な場合は、システムのデフォルトの方式を使う場合が多い。 -- あくまでも「1文字(char/int)」が入出力の単位。実用上は「文字列(String)」単位で扱うことが多く、後述のBufferedReaderやPrintWriteを使うと便利。 自分なりのイメージとしては、バイト単位の入出力機能がまずベースとしてあり、その上に文字符号化方式と組み合わせることで文字単位の入出力機能が存在すると考えると分かりやすい。 参考資料: - Java 入門 | I/O Streams -- http://nextindex.jp/java/IO/index.html - "JDK 6 ドキュメント" > "機能リファレンスガイド" > "基本ライブラリ" > "その他のパッケージ" > "I/O" > "拡張" > "以前の拡張機能" > "文字ストリーム (JDKTM 1.1 から導入)" -- http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/io/io.html - JDK 6 I/O 関連 API & 開発者ガイド -- http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/io/index.html - Lesson: Basic I/O (The Java(TM) Tutorials > Essential Classes) -- http://download.oracle.com/javase/tutorial/essential/io/index.html * バイト単位の入出力(InputStream/OutputStream系) : 入力(InputStream) : #block||> 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")); ||< : 出力(OutputStream) : #block||> 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を使っても問題ない。 * 文字単位の入出力(Reader/Writer系) : 入力(Reader) : #block||> 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(); ||< : 出力(Writer) : #block||> 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を組み合わせることになる。 * 文字列(行)単位出力の便利クラス(PrintWriter/PrintStream) プリミティブ型やインスタンスを文字列に変換して出力してくれる便利クラス: java.io.PrintWriter : 任意の文字符号化方式と組み合わせられる java.io.PrintStream : システムのデフォルトの文字符号化方式に固定される print()が改行なし、println()が改行付き。テキスト出力に便利で、前述のようにBufferedWriterをラップして使ったりする。 : PrintStream/PrintWriterのどちらを使えば良いか? : PrintStreamは主にデバッグ用途で使われ、System.err, System.out で使われている。通常はPrintWriterを使えば良い。 * トークン単位の読み込み(構文解析用) java.io.StreamTokenizerのJavaDocを参照。 * 標準入出力と外部プロセスの起動 標準入出力: System.in : java.io.InputStream System.out : java.io.PrintStream System.err : java.io.PrintStream 外部プロセスの起動: - java.lang.Runtimeクラスのexec()メソッドファミリーを使う。 - Runtimeは直接インスタンスを生成できない。getRuntime()により取得する。 - exec()メソッドでは実行ファイルと引数、環境変数、作業ディレクトリを指定できる。 -- 環境変数と作業ディレクトリは省略できる。 -- 環境変数は"KEY=VALUE"形式の文字列の配列で指定する。 - exec()メソッドの戻り値は java.lang.Process クラスのインスタンス。 起動した外部プロセスの標準入出力との対話: : Process#getOutputStream() : 外部プロセスの標準入力 = 呼び出し側から見ればwrite先となるので、OutputStreamが返される。 : Process#getInputStream() : 外部プロセスの標準出力 = 呼び出し側から見ればread元となるので、InputStreamが返される。 : Process#getErrorStream() : 外部プロセスの標準エラー出力 = 呼び出し側から見ればread元となるので、InputStreamが返される。 JDK1.5以上の場合、環境変数をKey/ValueのMapで指定できるなど、使いやすくラップしてくれるProcessBuilderクラスが利用出来る。 * サンプル バイト単位・文字単位・文字列単位の入出力サンプルと、FilterInput/OutputStreamを使った16進ダンプのサンプル。 ** バイト単位・文字単位・文字列単位の入出力サンプル Foo.java: #code|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を使った16進ダンプのサンプル FilterInput/OutputStreamの派生クラスで、read()/write()に渡されたデータを16進ダンプする。 FileInput/OutputStreamをラップすることで、ファイルから読み込んだ直後と、書きこむ直前のバイナリデータを確認できる。今回はFilterInput/OutputStreamからの派生にしたが、BufferedInput/OutputStreamからの派生でもよいだろう。 ただし、このサンプルはラップが多段になり「やり過ぎ」とも言える。効率面で問題になる可能性がある。実際に使うのであれば、デバッグ用であることを明示するか、直接FileInput/OutputStreamから派生させてラップ段数を節約するなど工夫が必要。 Bar.java: #code|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(); } } ||< 実行結果は省略。 #navi_footer|Java|