こんにちは。開発ブログ運営担当のktです。

ファイルを読み込む処理のプログラムは今までも書いてきましたが、
そういえば後ろ(最後の行)から読み込むことはしてきませんでした。
今回その必要があったので、どのような手段があるか調べてみました。
調べてみると下記2つの方法が見つかったので、試してみようと思います。

・Apache Commons IOライブラリのReversedLinesFileReaderクラスを利用する方法。
・Java標準ライブラリのRandomAccessFileクラスを利用する方法。

環境

Windows10
Java17
Eclipse2022
※新しいJavaやEclipseを試してみたかっただけです。
 今回使った「Apache Commons IO 2.11.0」の利用はJava8以上が必要です。

読み込むファイル(target.csv)は下記。

社員ID,名前,誕生日
10001,ソフテム太郎,19700401
10002,ソフテム花子,19850606
10003,ソフテム次郎,19931120
99999,,

1.Apache Commons IOライブラリを利用する方法

①下記URLからCommons IOライブラリをダウンロードしてビルドパスに追加する。
https://commons.apache.org/proper/commons-io/
もしくはMavenやGradleで依存を解決する。
https://commons.apache.org/proper/commons-io/dependency-info.html

②そして、Commons IOライブラリを利用したコードが下記です。

package jp.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

import org.apache.commons.io.input.ReversedLinesFileReader;

public class LastLineReadSample {

	private static final String TARGET_FILE_PATH = "src/main/resources/target.csv";

	public static void main(String[] args) {
		
		var path = Path.of(TARGET_FILE_PATH);
		long startTime = System.currentTimeMillis();
		try(var reversedLinesFileReader = new ReversedLinesFileReader(path, StandardCharsets.UTF_8)) {
			String line;
			while ((line = reversedLinesFileReader.readLine()) != null) {
				System.out.println(line);
				if (line.contains("99999")) {
					break;
				}
			}
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}

		long endTime = System.currentTimeMillis();
		System.out.println("処理時間(ms):" + (endTime - startTime));
	}

}

③実行結果

99999,,
処理時間(ms):19

④説明
ソースの17行目でReversedLinesFileReaderクラスのインスタンスを生成して、
19行目のreadLineメソッドでファイルの後ろから1行ずつ読み込んでいます。
BufferedReaderと同じ感じで使うことができます。

2.Java標準ライブラリを利用する方法

①標準ライブラリのRandomAccessFileは、importするだけで利用可能です。

②RandomAccessFileを利用したコードが下記です。

package jp.example;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;

public class LastLineReadSample2 {

	private static final String TARGET_FILE_PATH = "src/main/resources/target.csv";

	public static void main(String[] args) {
		
		var path = Path.of(TARGET_FILE_PATH);
		long startTime = System.currentTimeMillis();
		try(var randomAccessFile = new RandomAccessFile(path.toFile(), "r")) {
			long length = randomAccessFile.length();
			randomAccessFile.seek(length - 9);
			String line = randomAccessFile.readLine();
			System.out.println(line);
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}

		long endTime = System.currentTimeMillis();
		System.out.println("処理時間(ms):" + (endTime - startTime));
	}

}

③実行結果

99999,,
処理時間(ms):0

④説明

ソースの15行目でRandomAccessFileクラスのインスタンスを生成して、
16行目のlegthメソッドでファイルのバイト数を取得しています。
17行目のseekメソッドでファイルの読み込み開始位置を指定しています。
ファイルのバイト数から9バイト(“99999,,”と改行コードの\r\nで9バイト)を引いています。
処理時間はCommons IOライブラリよりもかかっていません。

まとめ

Commons IOライブラリを利用するかJava標準ライブラリを利用するかは、
その時の要件次第で使い分ければいいかと思います。
でもファイルを後ろから読み込むことはあまりしないかもしれませんね。
ログファイルの直近数行分を読んで何かするとかで利用するそうです。
標準ライブラリでもまだまだ利用したことがないクラスがあるし、
Javaのバージョンアップでどんどん新しい機能も追加されていて、
まだまだ勉強しなくちゃなと思いました。