首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實作檔案反向讀取(Tail 功能)完整程式碼解析

     編輯:Java 相關知識

需求背景:高效讀取大型日誌檔案的最後 N 行

在運維與開發中,常需「查看日誌尾部」或「分頁倒序讀取」。將整個檔案載入記憶體不現實,需使用 RandomAccessFile 從檔案末尾向前讀取。

核心參數說明

  • end:讀取終點位移(byte),null 代表從檔案最末尾開始。
  • num:欲讀取的行數。
  • file:目標檔案物件。
  • charset:字元編碼,預設 UTF-8。

使用範例

// 讀取最後 10 行
Map result = FileUtil.tail(null, 10, file, null);
List<Object[]> lines = (List) result.get(FileUtil.ARR);

// 接著向上再讀 3 行(第 11~13 行)
FileUtil.tail((Long) result.get(FileUtil.POINT), 3, file, null);

核心實作 FileUtil.tail()

public class FileUtil {
    private static final long STEP = 5000; // 每次向前讀取的位元組塊大小
    public static final String ARR = "arr";      // 回傳行資料
    public static final String POINT = "point";  // 下一次讀取的起始位移

    public static Map<String, Object> tail(Long end, long num, File file, String charset) throws Exception {
        if (num <= 0 || (end != null && end < 0)) throw new IllegalArgumentException();

        Map<String, Object> map = new HashMap<>();
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(file, "r");
            long fileEnd = (end == null ? raf.length() : end);
            long readPos = fileEnd > STEP ? fileEnd - STEP : 0;

            raf.seek(readPos);
            LinkedList<Object[]> queue = new LinkedList<>(); // 固定大小隊列,存放 [位移, 行內容]
            String line;
            int skippedFirst = 0;

            while ((line = raf.readLine()) != null) {
                // 若非從 0 開始讀,第一行通常不完整,跳過
                if (skippedFirst++ == 0 && readPos != 0) continue;

                long pointer = raf.getFilePointer();
                if (pointer >= fileEnd && end != null) break; // 超過指定終點

                Object[] entry = new Object[2];
                entry[0] = pointer;
                entry[1] = new String(line.getBytes("ISO-8859-1"), charset == null ? "UTF-8" : charset);

                if (queue.size() == num) queue.poll(); // 維持固定大小
                queue.offer(entry);
            }

            // 讀不夠 num 行且還有資料,遞迴向前繼續讀
            if (queue.size() < num && readPos > 0) {
                long need = num - queue.size();
                Object[] first = queue.peek();
                if (first == null) throw new RuntimeException("STEP 太小,無法定位完整行");
                Map<String, Object> more = tail((Long) first[0], need, file, charset);
                map.put(POINT, more.get(POINT));
                map.put(ARR, ArrayUtils.addAll((Object[]) more.get(ARR), queue.toArray()));
            } else if (!queue.isEmpty()) {
                map.put(POINT, queue.peek()[0]);
                map.put(ARR, queue.toArray());
            }
        } finally {
            if (raf != null) try { raf.close(); } catch (IOException ignored) {}
        }
        return map;
    }
}

關鍵設計細節

  1. 塊狀讀取 + 隊列緩衝:每次回溯 STEP 位元組,用 LinkedList 維持最新 num 行。
  2. 首行不完整處理:隨機定位通常落在行中間,首行 readLine() 讀到的是片段,直接丟棄。
  3. 編碼轉換:readLine() 回傳 ISO-8859-1 位元組陣列,再用目標編碼重組字串。
  4. 遞迴補齊:單次回溯不足時,以最早那行的位移為新 end 遞迴呼叫。
  5. 資源釋放:finally 區塊確保 RandomAccessFile 關閉。

適用場景與限制

  • ✅ 適合:大型日誌、即時尾部監控、分頁倒序顯示。
  • ⚠️ 注意:極長單行(超過 STEP)會導致讀取失敗,需調大 STEP 或改用緩衝讀取。
  • ⚠️ 編碼:預設 UTF-8,含 BOM 或特殊編碼檔案需自行處理。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved