首頁 > 軟體

java.nio.file.WatchService 實時監控檔案變化的範例程式碼

2022-05-30 14:01:40

在平時的開發過程中,會有很多場景需要實時監聽檔案的變化,如下:
1、通過實時監控 mysql 的 binlog 紀錄檔實現資料同步
2、修改組態檔後,希望系統可以實時感知
3、應用系統將紀錄檔寫入檔案中,紀錄檔監控系統可以實時抓取紀錄檔,分析紀錄檔內容並進行報警
4、類似 ide 工具,可以實時感知管理的工程下的檔案變更

在 Java 語言中,從 JDK7 開始,新增了java.nio.file.WatchService類,用來實時監控檔案的變化。

1.範例程式碼

FileWatchedService 類:

package org.learn.file;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;

/**
 * 實時監控檔案的變化
 *
 * @author zhibo
 * @date 2019-07-30 20:37
 */
public class FileWatchedService {
    private WatchService watchService;

    private FileWatchedListener listener;

    /**
     *
     * @param path 要監聽的目錄,注意該 Path 只能是目錄,否則會報錯 java.nio.file.NotDirectoryException: /Users/zhibo/logs/a.log
     * @param listener 自定義的 listener,用來處理監聽到的建立、修改、刪除事件
     * @throws IOException
     */
    public FileWatchedService(Path path, FileWatchedListener listener) throws IOException {
        watchService = FileSystems.getDefault().newWatchService();
        path.register(watchService,
                /// 監聽檔案建立事件
                StandardWatchEventKinds.ENTRY_CREATE,
                /// 監聽檔案刪除事件
                StandardWatchEventKinds.ENTRY_DELETE,
                /// 監聽檔案修改事件
                StandardWatchEventKinds.ENTRY_MODIFY);
//
//            path.register(watchService,
//                    new WatchEvent.Kind[]{
//                            StandardWatchEventKinds.ENTRY_MODIFY,
//                            StandardWatchEventKinds.ENTRY_CREATE,
//                            StandardWatchEventKinds.ENTRY_DELETE
//                    },
//                    SensitivityWatchEventModifier.HIGH);

        this.listener = listener;
    }

    private void watch() throws InterruptedException {
        while (true) {
            WatchKey watchKey = watchService.take();
            List<WatchEvent<?>> watchEventList = watchKey.pollEvents();
            for (WatchEvent<?> watchEvent : watchEventList) {
                WatchEvent.Kind kind = watchEvent.kind();

                WatchEvent<Path> curEvent = (WatchEvent<Path>) watchEvent;
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    listener.onOverflowed(curEvent);
                    continue;
                } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    listener.onCreated(curEvent);
                    continue;
                } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                    listener.onModified(curEvent);
                    continue;
                } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
                    listener.onDeleted(curEvent);
                    continue;
                }
            }

            /**
             * WatchKey 有兩個狀態:
             * {@link sun.nio.fs.AbstractWatchKey.State.READY ready} 就緒狀態:表示可以監聽事件
             * {@link sun.nio.fs.AbstractWatchKey.State.SIGNALLED signalled} 有資訊狀態:表示已經監聽到事件,不可以接續監聽事件
             * 每次處理完事件後,必須呼叫 reset 方法重置 watchKey 的狀態為 ready,否則 watchKey 無法繼續監聽事件
             */
            if (!watchKey.reset()) {
                break;
            }

        }
    }

    public static void main(String[] args) {
        try {
            Path path = Paths.get("/Users/zhibo/logs/");
            FileWatchedService fileWatchedService = new FileWatchedService(path, new FileWatchedAdapter());
            fileWatchedService.watch();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

FileWatchedListener 類:

package org.learn.file;

import java.nio.file.Path;
import java.nio.file.WatchEvent;

public interface FileWatchedListener {
    void onCreated(WatchEvent<Path> watchEvent);

    void onDeleted(WatchEvent<Path> watchEvent);

    void onModified(WatchEvent<Path> watchEvent);

    void onOverflowed(WatchEvent<Path> watchEvent);
}

FileWatchedAdapter 類:

package org.learn.file;

import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * 檔案監聽介面卡
 *
 * @author zhibo
 * @date 2019-07-31 11:07
 */
public class FileWatchedAdapter implements FileWatchedListener {

    @Override
    public void onCreated(WatchEvent<Path> watchEvent) {
        Path fileName = watchEvent.context();
        System.out.println(String.format("檔案【%s】被建立,時間:%s", fileName, now()));
    }

    @Override
    public void onDeleted(WatchEvent<Path> watchEvent) {
        Path fileName = watchEvent.context();
        System.out.println(String.format("檔案【%s】被刪除,時間:%s", fileName, now()));
    }

    @Override
    public void onModified(WatchEvent<Path> watchEvent) {
        Path fileName = watchEvent.context();
        System.out.println(String.format("檔案【%s】被修改,時間:%s", fileName, now()));
    }

    @Override
    public void onOverflowed(WatchEvent<Path> watchEvent) {
        Path fileName = watchEvent.context();
        System.out.println(String.format("檔案【%s】被丟棄,時間:%s", fileName, now()));
    }

    private String now(){
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        return dateFormat.format(Calendar.getInstance().getTime());
    }
}

執行以上程式碼,啟動監控任務,然後我在/Users/zhibo/logs/目錄中建立、修改、刪除檔案,命令如下:

應用程式感知到檔案變化,列印紀錄檔如下:

2.其實並沒有實時

大家可以看到,監控任務基本上是以 10 秒為單位進行紀錄檔列印的,也就是說修改一個檔案,WatchService 10秒之後才能感知到檔案的變化,沒有想象中的那麼實時。根據以上的經驗,推測可能是 WatchService 做了定時的操作,時間間隔為 10 秒。通過翻閱原始碼發現,在 PollingWatchService 中確實存在一個固定時間間隔的排程器,如下圖:

該排程器的時間間隔有 SensitivityWatchEventModifier 進行控制,該類提供了 3 個級別的時間間隔,分別為2秒、10秒、30秒,預設值為 10秒。SensitivityWatchEventModifier 原始碼如下:

package com.sun.nio.file;

import java.nio.file.WatchEvent.Modifier;

public enum SensitivityWatchEventModifier implements Modifier {
    HIGH(2),
    MEDIUM(10),
    LOW(30);

    private final int sensitivity;

    public int sensitivityValueInSeconds() {
        return this.sensitivity;
    }

    private SensitivityWatchEventModifier(int var3) {
        this.sensitivity = var3;
    }
}

通過改變時間間隔來進行驗證,將

        path.register(watchService,
                /// 監聽檔案建立事件
                StandardWatchEventKinds.ENTRY_CREATE,
                /// 監聽檔案刪除事件
                StandardWatchEventKinds.ENTRY_DELETE,
                /// 監聽檔案修改事件
                StandardWatchEventKinds.ENTRY_MODIFY);

修改為:

    path.register(watchService,
                    new WatchEvent.Kind[]{
                            StandardWatchEventKinds.ENTRY_MODIFY,
                            StandardWatchEventKinds.ENTRY_CREATE,
                            StandardWatchEventKinds.ENTRY_DELETE
                    },
                    SensitivityWatchEventModifier.HIGH);

檢視紀錄檔,發現正如我們的推斷,WatchService 正以每 2 秒的時間間隔感知檔案變化。
在 stackoverflow 中也有人提出了該問題,問題:Is Java 7 WatchService Slow for Anyone Else,我的 mac 系統中確實存在該問題,由於手頭沒有 windows、linux 系統,因此無法進行這兩個系統的驗證。

到此這篇關於java.nio.file.WatchService 實時監控檔案變化的文章就介紹到這了,更多相關java.nio.file.WatchService 監控檔案變化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com