(この記事は 地平線に行く とのマルチポストです)
Java でファイルを読み込む処理は、バージョンが上がるごとにどんどん簡単に書けるようになっていきました。
今回は、どれだけ簡単になっていったかを Java のバージョンごとにまとめて説明します。
なお、ここでは以下の処理を行うコードをもとにしています。
- そこそこ大きいテキストファイルを一行ずつ読み込む
- 文字コードは
UTF-8
Java 1.1, Java 1.2, Java 1.3
public static void main(String[] args) throws IOException {
File file = new File(args[0]);
BufferedReader reader = null;
try {
reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(file)
, "UTF-8"));
String line;
while((line = reader.readLine()) != null) {
// 処理
}
} finally {
if (reader != null) {
reader.close();
}
}
}
長いですね。
この時代に FileReader
というファイルからテキストを読み込むクラスがあるにはあります。
ただ、このクラスでは 問答無用でシステムデフォルトの文字コードが使われてしまいます。
そのため、Windows から Linux に持っていった場合に文字化けが起きる問題がよく起こっていました。
それを避けるためには、文字コードの指定ができる InputStreamReader
を使う必要があります。
InputStreamReader
自体ではファイルの読み込みができないので、FileInputStream
を使う必要があります。
さらに、一行ずつ読み込むためには BufferedReader
を使う必要があります。これ自体はバッファしかしてくれません。
なので、結果として FileInputStream
→ InputStreamReader
→ BufferedReader
とラップしていく必要があります。
Java 1.4, Java 1.5, Java 6
public static void main(String[] args) throws IOException {
File file = new File(args[0]);
Charset charset = Charset.forName("UTF-8");
BufferedReader reader = null;
try {
reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(file), charset));
String line;
while ((line = reader.readLine()) != null) {
// 処理
}
} finally {
if (reader != null) {
reader.close();
}
}
}
Charset
クラスができました。
これにより、文字コードを型で表せるようになりました。
ただ、UTF-8
を使う場合には Charset.forName("UTF-8")
とする必要がありました。
なので、型安全ではあるけれど、文字列で指定した方が楽という状況でした。
Java 7
public static void main(String[] args) throws IOException {
Path path = Paths.get(args[0]);
Charset charset = StandardCharsets.UTF_8;
try (BufferedReader reader = Files.newBufferedReader(path, charset)){
String line;
while ((line = reader.readLine()) != null) {
// 処理
}
}
}
一気にシンプルになりました。
まず、StandardCharsets
クラスが追加されました。これにより、標準の文字コード1であればわざわざ文字列で指定せずに済むようになりました。
また、Files
クラスと newBufferedReader(path, charset)
メソッドが追加され、いろんなクラスを new
する必要がなくなりました。2
あと、このバージョンで Path
クラスが追加されました。
ファイルを読み込むだけだと File
クラスと大差ないですが、パスの操作が簡単になりました。
さらに、try-with-resources
が追加され、めんどくさかった close
処理をいい感じにやってくれるようになりました。
----
ちなみに…。
このバージョンから まとめて読み込んで List<String>
に格納してくれる Files.readAllLines(Path, Charset)
も使えるようになりました。
ただし、これは大きなファイルに使うとメモリを食ってしまうので要注意です。
Java 8, Java 9, Java 10
public static void main(String[] args) throws IOException {
Path path = Paths.get(args[0]);
try (Stream<String> stream = Files.lines(path)){
stream.forEach(line -> /* 処理 */);
}
}
Stream
クラスによって、行の読み込みと処理を反復させることができるようになりました。
これのおかげで、1行ずつ処理をしたいという場合にとてもシンプルに書けるようになりました。
また、Files
クラスを使う際に、UTF-8
であれば文字コードの指定を省略できるようになりました。
InputStreamReader
などは省略時にシステムのデフォルト文字コードが指定されたものとみなされますが、こちらは UTF-8
を指定したものとみなされる という点には注意が必要です。
ただ、最近のテキストファイルが UTF-8
であることを踏まえると、妥当な判断かなと思います。
Java 11 ~
public static void main(String[] args) throws IOException {
Path path = Path.of(args[0]);
try (Stream<String> stream = Files.lines(path)){
stream.forEach(line -> /* 処理 */);
}
}
Path.of(String first, String... more)
というメソッドが追加されました。
内部の処理は Paths.get(String first, String... more)
とまったく同じですが、このおかげで Path
クラスを使うにはどうすればいいんだっけと迷わずに済むようになりました。
----
上記のサンプルでは使っていないのですが…。
なんと、このバージョンで FileReader
クラスのコンストラクタで Charset
が指定できるようになりました。
Java 1.1 のときに欲しかった…。
なお、InputStreamReader
クラスのように文字コードを "UTF-8" のような文字列で指定することはできません。
既存のクラスは互換性のために文字列でも Charset
でも指定できるようになっていますが、今後追加されるクラスは Charset
のみになるようです。
まとめ
Java はファイルを読み込むだけでもなんて面倒くさいんだと言われていました。
でも、それは昔のお話。
今ではとても簡単になっているんだよ、というのが伝われば幸いです。