WatchService でサブディレクトリを再帰的に監視する


  • WatchService では、指定ディレクトリの監視が可能
  • サブディレクトリを含めて監視するには、再帰的に監視ディレクトリを WatchService に登録する必要がある

  • Windows では、WatchService に登録したディレクトリはロックされ、ディレクトリ名のリネームなどが行えない

  • Windows では、ReadDirectoryChangesW 関数が使われており、bWatchSubtree パラメータが指定できる
    • TRUE の場合、指定されたディレクトリにルート化されたディレクトリ ツリーを監視する
  • ExtendedWatchEventModifier.FILE_TREE を指定することで、Windows に限り、サブディレクトリを WatchService に登録することなく、サブディレクトリを含めた監視が行える


実装例は以下

import java.nio.file.*;
import com.sun.nio.file.ExtendedWatchEventModifier;
import static java.lang.IO.println;

void main() throws Exception {

    Path watchPath = Path.of(".");

    try (WatchService watchService = FileSystems.getDefault().newWatchService()) {

        WatchEvent.Kind<?>[] kinds = new WatchEvent.Kind<?>[] {
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.ENTRY_MODIFY
        };
        WatchEvent.Modifier[] modifiers = new WatchEvent.Modifier[] {
            // windows only
            ExtendedWatchEventModifier.FILE_TREE
        };
        watchPath.register(watchService, kinds, modifiers);
        println("watchPath : " + watchPath);

        for (;;) {

            WatchKey watchKey = watchService.take();
            Path dir = (Path) watchKey.watchable();

            for (WatchEvent<?> event : watchKey.pollEvents()) {

                WatchEvent.Kind<?> kind = event.kind();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    println("OVERFLOW");
                    continue;
                }

                Path path = dir.resolve((Path) event.context());

                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    println("CREATE : " + path);
                } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
                    println("DELETE : " + path);
                } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                    println("MODIFY : " + path);
                } else {
                    println("unknown kind: " + kind);
                }
            }

            boolean valid = watchKey.reset();
            if (!valid) {
                break;
            }
        }
    }
}


  • FileSystems..newWatchService()WatchService を取得
  • Path.register()WatchService に自身を監視対象として登録
    • 監視イベントStandardWatchEventKinds を指定
    • Windows の場合は xtendedWatchEventModifier.FILE_TREE でサブツリーを対象に指定
  • watchService.take()WatchKey を取得
    • このメソッドはファイル・システムの変更までブロックする
  • watchKey.pollEvents() で監視対象ディレクトリに発生したイベントを取得する
  • ファイル・システムの変更が多く、処理が間に合わなかった(内部的に保持しているイベントキューの溢れ)場合は OVERFLOW イベントが含まれる(WatchEvent.Kind<Object>)
  • OVERFLOW イベント以外は、監視対象として登録したイベントで、event.context() によりパスが取得できる
  • take() で取得した WatchKey は、reset() することで、再び監視対象となる
    • reset() の結果が valid ではない場合は、対象ディレクトリが消されたなどで無効となっている