⛄️

Reader/Writer/InputStream/OutputStream

2015/03/05に公開

少し話題に出たのでファイル読み書きなどでよく使う感じのアレをアレしたいと思います。

まず、

  • InputStream/OutputStreamはバイナリデータのストリームです。byteで読み書きします。
  • Reader/Writerはテキストデータのストリームです。charで読み書きします。
  • ストリームというのはbytecharを使って少しずつデータを読み込んだり書き出したりするためのものです。

というのが基本になります。

ファイルの読み書き

テキストファイルを読み込む

Path path = Paths.get("path/to/file");
try (BufferedReader in = Files.newBufferedReader(path)) {
    //読み込み処理
}

ファイルはUTF-8で読み込まれます。

UTF-8以外のファイルを読み込む場合は第二引数にCharsetを渡します。

try (BufferedReader in = Files.newBufferedReader(path, Charset.forName("Windows-31J"))) {
    String line;
    while(null != (line = in.readLine())) {
        //読み込み処理
    }
}

バイナリファイルを読み込む

try (InputStream in = new BufferedInputStream(Files.newInputStream(path))) {
    byte[] b = new byte[1000];
    int i;
    while (-1 != (i = in.read(b))) {
        //読み込み処理
    }
}

Files.newInputStreamはバッファリングされないので巨大なファイルやたくさんファイルを扱う処理だと遅いと思います。
基本的にはBufferedInputStreamでラップする方が良いかとー。

テキストファイルを書き出す

try (BufferedWriter out = Files.newBufferedWriter(path)) {
    //書き出し処理
}

バイナリファイルを書き出す

try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(path))) {
    //書き出し処理
}

Files.newInputStreamと同じくFiles.newOutputStreamもバッファリングされません。

オンメモリで扱う

Writerを渡したらそこにもろもろ書き出してくれるライブラリがあるんだけどわざわざファイルに書き出すんじゃなくてオンメモリで処理してStringで結果を取りたいんや! というような場合にはStringWriterを使います。

StringWriter out = new StringWriter();
library.writeTo(out);
String result = out.toString();

Reader/InputStream/OutputStreamにもそれぞれオンメモリで使用するためのクラスがあります。

  • StringReaderStringを読み込めるReader
  • StringWriterStringへ書き出せるWriter
  • ByteArrayInputStreambyte[]を読み込めるInputStream
  • ByteArrayOutputStreambyte[]へ書き出せるOutputStream

InputStreamをReaderへ/OutputStreamをWriterへ変換する

それぞれInputStreamReaderOutputStreamWriterを使って変換できます。

InputStream in = ...
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);

OutputStream out = ...
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);

第二引数にCharsetを渡していますが、何も渡さない場合はデフォルトエンコーディングが使用されるので注意が必要です。
デフォルトエンコーディングとはシステムプロパティfile.encodingで取得できるものです。
変更したい場合は次のようにJava起動時にオプションを設定します。

java -Dfile.encoding=UTF-8 com.example.MainClass

ZIPファイルを読み込む/書き出す

ZIPファイルの読み書きにはZipInputStreamZipOutputStreamが使えます。

InputStream in = ...
try(ZipInputStream zin = new ZipInputStream(in, StandardCharsets.UTF_8)) {
    ZipEntry zipEntry;
    while (null != (zipEntry = zin.getNextEntry())) {
        byte[] b = new byte[1000];
        int i;
        while (-1 != (i = zin.read(b))) {
            //読み込み処理
        }
    }
}
OutputStream out = ...
try(ZipOutputStream zout = new ZipOutputStream(out, StandardCharsets.UTF_8)) {
    ZipEntry zipEntry = new ZipEntry("hoge.txt");
    zout.putNextEntry(zipEntry);
    byte[] b = ...
    zout.write(b);
    zout.closeEntry();
}

ファイルのコピー、移動をする

Filesを使います。

Path src = ...
Path dest = ...

Files.copy(src, dest);

Files.move(src, dest);

Channel

Reader/Writer/InputStream/OutputStreamの他にChannelというものもありますがChannelが必要になるライブラリには
ほぼ出会った事がないので覚えなくても生きて行けると思います。

おまけ

テキストファイルの読み込みにはFiles.newBufferedReaderを使うと書きましたがJava 6まではFileReaderを使って次のようにファイル読み込みをしていました。

File file = new File("path/to/file");
Reader in = new FileReader(file);
try {
    //読み込み処理
} finally {
    in.close();
}

Charsetを渡さずにFileReaderをインスタンス化していますが、この場合はデフォルトエンコーディングが使われていました。

しかもFileReaderにはCharsetを受け取るコンストラクタは用意されていません。
ではデフォルトエンコーディング以外でファイルを読み込みたい場合はどうするのか?

その場合は、

  1. FileInputStreamでファイルを開いて
  2. InputStreamReaderCharsetを指定しつつラップする

という方法をとっていました。

File file = new File("path/to/file");
Reader in = new InputStreamReader(new FileInputStream(file), Charset.forName("iso-2022-jp"));
try {
    //読み込み処理
} finally {
    in.close();
}

そういう訳でjava.ioCharsetを受け取らない場合はデフォルトエンコーディング、java.nio.fileCharsetを受け取らない場合はUTF-8が使われる、という感じです。

デフォルトエンコーディングは環境によって変わるのでjava.nio.fileを使っておくのが安全だと思います。

Discussion