Java

Java ログファイルを前回読み込んだ最終位置から読み込み始める

はじめに

何GBにもなるようなログファイルを毎回先頭から読み込み直していると大変です。
前回読み込んだ最終位置から読み込み開始できるようにします。

事前に準備する外部ライブラリ等はありません。
JavaSEに含まれるjava.io.RandomAccessFileクラスを使用します。

ログファイルは以下のファイルを使います。

input.log

三色団子1本目
三色団子2本目
三色団子3本目
三色団子4本目
三色団子5本目
三色団子6本目
三色団子7本目
三色団子8本目
三色団子9本目
三色団子10本目
三色団子11本目
三色団子12本目
三色団子13本目
三色団子14本目
三色団子15本目
三色団子16本目
三色団子17本目
三色団子18本目
三色団子19本目
三色団子20本目

実装例

サンプルでは、動作確認しやすいようにmainメソッドで実行できるようにしてあります。

LogReader.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 *
 * @author tool-taro.com
 */
public class LogReader {

	public static void main(String[] args) throws FileNotFoundException, IOException {

		//読み込みたいログファイル
		String logFilePath = "input.log";
		//エンコーディング
		String encoding = "UTF-8";
		//読み込んだ前回位置
		long pointer = 0;
		//読み込んだ最終位置
		long nextReturn;

		RandomAccessFile reader = null;
		int lineNumber = 0;
		String line;
		byte[] bytes;

		try {
			//初回は先頭から読み込む
			reader = new RandomAccessFile(new File(logFilePath), "r");
			while (true) {
				line = reader.readLine();
				//読み込んだ位置を記録する
				nextReturn = reader.getFilePointer();
				if (line == null) {
					break;
				}

				//前回読み込んだ位置に戻り、今回の読み取り範囲をbyte配列で取得する(任意の文字コードで文字列に変換するため)
				{
					reader.seek(pointer);
					bytes = new byte[(int) (nextReturn - pointer)];
					reader.read(bytes);
					pointer = reader.getFilePointer();
					line = new String(bytes, encoding);
					//末尾の改行コードを除去
					while (line.endsWith("\r") || line.endsWith("\n")) {
						line = line.substring(0, line.length() - 1);
					}
				}

				++lineNumber;
				System.out.format("1回目 %1$d行目=[%2$s]\n", lineNumber, line);
				//10行読み込んだら終了してみる
				if (lineNumber >= 10) {
					break;
				}
			}
			reader.close();
			System.out.println("--------------");
			System.out.format("ポインタの位置=%1$d\n", pointer);
			System.out.println("--------------");

			//2回目は前回の最終位置から読み込む
			reader = new RandomAccessFile(new File(logFilePath), "r");
			reader.seek(pointer);
			while (true) {
				line = reader.readLine();
				//読み込んだ位置を記録する
				nextReturn = reader.getFilePointer();
				if (line == null) {
					break;
				}

				//前回読み込んだ位置に戻り、今回の読み取り範囲をbyte配列で取得する(任意の文字コードで文字列に変換するため)
				{
					reader.seek(pointer);
					bytes = new byte[(int) (nextReturn - pointer)];
					reader.read(bytes);
					pointer = reader.getFilePointer();
					line = new String(bytes, encoding);
					//末尾の改行コードを除去
					while (line.endsWith("\r") || line.endsWith("\n")) {
						line = line.substring(0, line.length() - 1);
					}
				}

				++lineNumber;
				System.out.format("2回目 %1$d行目=[%2$s]\n", lineNumber, line);
			}
		}
		finally {
			if (reader != null) {
				try {
					reader.close();
				}
				catch (Exception e) {
				}
			}
		}
	}
}

RandomAccessFileクラスでは、readLineする際の文字コードの指定ができません。
その対策として、改行を検知してから一旦戻って読み込み直しているあたり、少し横着な実装にはなってしまいました。

動作確認

$ LogReader.java
$ java LogReader
$ 1回目 1行目=[三色団子1本目]
1回目 2行目=[三色団子2本目]
1回目 3行目=[三色団子3本目]
1回目 4行目=[三色団子4本目]
1回目 5行目=[三色団子5本目]
1回目 6行目=[三色団子6本目]
1回目 7行目=[三色団子7本目]
1回目 8行目=[三色団子8本目]
1回目 9行目=[三色団子9本目]
1回目 10行目=[三色団子10本目]
--------------
ポインタの位置=211
--------------
2回目 11行目=[三色団子11本目]
2回目 12行目=[三色団子12本目]
2回目 13行目=[三色団子13本目]
2回目 14行目=[三色団子14本目]
2回目 15行目=[三色団子15本目]
2回目 16行目=[三色団子16本目]
2回目 17行目=[三色団子17本目]
2回目 18行目=[三色団子18本目]
2回目 19行目=[三色団子19本目]
2回目 20行目=[三色団子20本目]

指定通り、10行目でいったん終了し、再開時には続きから読み込んでいます。
NFS経由でファイルを読み込む場合などにおいては、データ転送にかかるトラフィックも大幅に下げることができます。

環境

  • 開発
    • Windows 10 Pro
    • JDK 1.8.0_74
    • NetBeans IDE 8.1
  • 動作検証
    • CentOS Linux release 7.2
    • JDK 1.8.0_74

Webツールも公開しています。
Web便利ツール@ツールタロウ

スポンサーリンク