前回の記事(Javaのバージョン別、1行ずつファイルを読む方法まとめ)への感想で、このような話がありました。
丸々読み込む方法のまとめもお願いします!
— ユースケ (山本裕介) (@yusuke) May 3, 2021
というわけで、今回はテキストファイルを一括で読み込む方法をまとめました。
(前回と被っている点は省略しているので、まだ読んでない人は先に前回の記事をどうぞ)
Java 1.1, 1.2, 1.3
private static String readString(File file) throws IOException { Reader reader = null; try { reader = new InputStreamReader(new FileInputStream(file), "UTF-8"); StringBuffer sb = new StringBuffer(); int len; char[] buffer = new char[1024 * 8]; while ((len = reader.read(buffer)) != -1) { sb.append(buffer, 0, len); } return sb.toString(); } finally { if (reader != null) { reader.close(); } } }
まだこのころはめんどくさいです。
FileInputStream
でファイルを読み込み、InputStreamReader
で指定した文字コードにデコードするようにしています。
読み込む際は、char[]
でバッファして StringBuffer
に追加していっています。
ちなみに、char[]
でバッファしながら読み込んでいるので、 BufferedReader
でラップする必要はありません。
Java 1.4, 1.5, 6
private static String readString(File file) throws IOException { CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); try (FileInputStream stream = new FileInputStream(file)) { FileChannel channel = stream.getChannel(); ByteBuffer bb = channel.map(MapMode.READ_ONLY, 0, channel.size()); CharBuffer cb = decoder.decode(bb); return cb.toString(); } }
ループがなくなって、だいぶ処理がすっきりしました。
このバージョンで追加された Charset
クラスから、CharsetDecoder
というデコーダを取得できます。
これに FileChannel#map(FileChannel.MapMode mode, long position, long size)
で取得した MappedByteBuffer
を渡すことで、ファイルを読み込めます。
MappedByteBuffer
はファイルの内容をメモリにマッピングして扱う(メモリマップドファイル)ので、大きなファイルの時に効率的に処理が行われます。
逆に言うと、小さいファイルを読み込む場合は性能が悪いです。
その場合は、今まで通り InputStream
で読み込みましょう。
ほとんどのオペレーティング・システムでは、ファイルをメモリーにマッピングするほうが、通常のreadメソッドまたはwriteメソッドを使って数十キロバイトのデータの読み込みまたは書込みを行うよりも負荷が大きくなります。 性能を重視するなら、比較的大きめのファイルだけをマッピングすることをお薦めします。 FileChannel (Java SE 15 & JDK 15))
----
ちなみに、この FileChannel
の map
メソッドを使うやり方はひしだまさんの記事を読んで知りました!
Javaバッファークラスメモ(Hishidama's Java Buffer Memo)
Java 7, 8, 9, 10
private static String readString(Path path) throws IOException { byte[] bytes = Files.readAllBytes(path); return new String(bytes, StandardCharsets.UTF_8); }
Files
クラスが追加され、これに一括でファイルを byte[]
として読み込むメソッドが用意されました。
これを使ってファイルを読み込めば、あとは String
クラスを new
するだけです。
ちなみに、このバージョンでファイルからすべての行を読み取る Files#readAllLines(Path, Charset)
も追加されました。
この戻り値は List<String>
です。行ごとに処理をしたい場合はこちらが便利です。
Java 11~
private static String readString(Path path) throws IOException { return Files.readString(path, StandardCharsets.UTF_8); }
ひとつのメソッドでファイルをすべて読み込めるようになりました。 1
このメソッドの実装では StringCoding
というインターナルなクラスを使っているので、new String(bytes, StandardCharsets.UTF_8)
とするよりも効率的な処理になっています。
まとめ
めんどくさかった処理も、Java のバージョンが上がるごとに簡単に書けるようになっています。
なので、積極的に新しい Java を使っていきましょう!