1. 程式人生 > >JDK 之 NIO 2 WatchService、WatchKey(監控檔案變化)

JDK 之 NIO 2 WatchService、WatchKey(監控檔案變化)

JDK 之 NIO 2 WatchService、WatchKey(監控檔案變化)

JDK 規範目錄(https://www.cnblogs.com/binarylei/p/10200503.html)

一、WatchService、WatchKey 使用

具體詳見:https://blog.csdn.net/lirx_tech/article/details/51425364

public static void main(String[] args) throws Exception {
    // 1. 獲取檔案系統監控器,啟動一個後臺執行緒輪詢
    WatchService watchService = FileSystems.getDefault().newWatchService();

    // 2. 註冊要監聽的事件型別,檔案增、刪、改
    Paths.get("C:\\Users\\len\\Desktop\\xdr").register(watchService,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.ENTRY_MODIFY);

    while (true) {
        // 3. 獲取準備好的事件,pool() 立即返回、take() 阻塞
        WatchKey watchKey = watchService.poll(2, TimeUnit.SECONDS);
        if (Objects.isNull(watchKey)) {
            continue;
        }
        // 4. 處理準備好的事件
        List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
        for (WatchEvent<?> event : watchEvents) {
            if (event.kind().name().equals(StandardWatchEventKinds.ENTRY_CREATE.name())) {
                System.out.println("create: " + event.context());
            } else if (event.kind().name().equals(StandardWatchEventKinds.ENTRY_MODIFY.name())) {
                System.out.println("modify: " + event.context());
            } else if (event.kind().name().equals(StandardWatchEventKinds.ENTRY_DELETE.name())) {
                System.out.println("delete: " + event.context());
            }
        }
        // 5. 重啟該執行緒,因為處理檔案可能是一個耗時的過程,因此呼叫 pool() 時需要阻塞監控器執行緒
        boolean valid = watchKey.reset();
        if (!valid) {
            break;
        }
    }
}

二、原理

WatchService類圖

  • AbstractWatchService 實現 WatchService 介面
  • WindowsWatchService 具體的實現,啟動 Poller 執行緒
  • Poller 執行緒,輪詢指定的目錄

(1) AbstractWatchService

// 等待要處理的事件 signaled keys waiting to be dequeued
private final LinkedBlockingDeque<WatchKey> pendingKeys = new LinkedBlockingDeque<WatchKey>();

@Override
public final WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException {
    checkOpen();
    WatchKey key = pendingKeys.poll(timeout, unit);
    checkKey(key);
    return key;
}

可以看到呼叫 poll 的時候直接從佇列中取 key,那就必然有一個執行緒往 pendingKeys 中塞資料。在 AbstractWatchService 中有一個 enqueueKey 方法往 pendingKeys 中塞資料。

final void enqueueKey(WatchKey key) {
    pendingKeys.offer(key);
}

(2) WindowsWatchService

AbstractWatchService 有不同的實現,以 WindowsWatchService 為例。

WindowsWatchService(WindowsFileSystem fs) throws IOException {
    // create I/O completion port
    long port = 0L;
    try {
        port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0);
    } catch (WindowsException x) {
        throw new IOException(x.getMessage());
    }
    this.poller = new Poller(fs, this, port);
    this.poller.start();
}

Poller 是 WindowsWatchService 的內部類,開啟了一個執行緒監控目錄。

(3) Poller

重點關注 Poller 中的 run 方法。

@Override
public void run() {
    for (;;) {
        CompletionStatus info;
        try {
            info = GetQueuedCompletionStatus(port);
        } catch (WindowsException x) {
            return;
        }

        WindowsWatchKey key = ck2key.get((int)info.completionKey());
        if (key == null) {
            continue;
        }
        boolean criticalError = false;
        if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
            key.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
        } else if (errorCode != 0 && errorCode != ERROR_MORE_DATA) {
            criticalError = true;
        } else {
            // 省略... (處理 error)
        }

        // 一切正常則 criticalError = true,此時將這個 WatchKey 加入 pendingKeys 中 
        if (criticalError) {
            implCancelKey(key);
            key.signal();
        }
    }
}

參考:

  1. 《NIO.2:WatchService、WatchKey(監控檔案變化)》:(https://blog.csdn.net/lirx_tech/article/details/51425364)

每天用心記錄一點點。內容也許不重要,但習慣很重要!