Reader/Writer/InputStream/OutputStream
少し話題に出たのでファイル読み書きなどでよく使う感じのアレをアレしたいと思います。
まず、
-
InputStream
/OutputStream
はバイナリデータのストリームです。byte
で読み書きします。 -
Reader
/Writer
はテキストデータのストリームです。char
で読み書きします。 - ストリームというのは
byte
やchar
を使って少しずつデータを読み込んだり書き出したりするためのものです。
というのが基本になります。
ファイルの読み書き
テキストファイルを読み込む
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
にもそれぞれオンメモリで使用するためのクラスがあります。
-
StringReader
はString
を読み込めるReader
-
StringWriter
はString
へ書き出せるWriter
-
ByteArrayInputStream
はbyte[]
を読み込めるInputStream
-
ByteArrayOutputStream
はbyte[]
へ書き出せるOutputStream
InputStreamをReaderへ/OutputStreamをWriterへ変換する
それぞれInputStreamReader
とOutputStreamWriter
を使って変換できます。
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ファイルの読み書きにはZipInputStream
とZipOutputStream
が使えます。
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
を受け取るコンストラクタは用意されていません。
ではデフォルトエンコーディング以外でファイルを読み込みたい場合はどうするのか?
その場合は、
-
FileInputStream
でファイルを開いて -
InputStreamReader
でCharset
を指定しつつラップする
という方法をとっていました。
File file = new File("path/to/file");
Reader in = new InputStreamReader(new FileInputStream(file), Charset.forName("iso-2022-jp"));
try {
//読み込み処理
} finally {
in.close();
}
そういう訳でjava.io
でCharset
を受け取らない場合はデフォルトエンコーディング、java.nio.file
でCharset
を受け取らない場合はUTF-8が使われる、という感じです。
デフォルトエンコーディングは環境によって変わるのでjava.nio.file
を使っておくのが安全だと思います。
Discussion