WatchService:ファイル変更の監視
WatchService を登録し、ファイルの作成、変更、削除イベントをリアルタイムに検出します。
「WatchService:ファイル変更の監視」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
WatchService とは
WatchServiceは、作成、変更、削除などのファイルシステムイベントをディレクトリで監視します。OSレベルの通知(Linuxではinotify、macOSではFSEvents)を使用するため、ポーリングよりもはるかに効率的です。
import java.nio.file.*;
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Path.of("/home/user/watched");
dir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE
);
System.out.println("Watching: " + dir);イベントのポーリング
イベントが発生するまでブロックするにはwatcher.take()を呼び出し、その後イベントを処理します。
while (true) {
WatchKey key;
try {
key = watcher.take(); // blocks until events arrive
} catch (InterruptedException e) {
break;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path changed = ((WatchEvent<Path>) event).context();
System.out.println(kind.name() + ": " + changed);
}
key.reset(); // must reset to receive further events
}ノンブロッキングポーリング
ブロックしないチェックにはwatcher.poll()を使用し、時間を指定して待機するにはpoll(timeout, unit)を使用します。
// Non-blocking — returns null if no events
WatchKey key = watcher.poll();
if (key != null) {
// process events
key.reset();
}
// Wait up to 5 seconds:
WatchKey key2 = watcher.poll(5, java.util.concurrent.TimeUnit.SECONDS);
if (key2 != null) {
// process events
key2.reset();
}OVERFLOWイベント
アプリケーションによる処理よりも速くイベントが蓄積すると、一部のイベントが失われたことをOVERFLOWイベントが通知します。適切に処理してください。
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
System.out.println("OVERFLOW: some events missed, rescanning directory");
// rescan dir to find current state
continue;
}
// process normal events
}完全な動作例
バックグラウンドスレッドで実行する、完全なファイル監視プログラムです。
class FileWatcher implements Runnable {
private final WatchService watcher;
FileWatcher(Path dir) throws IOException {
watcher = FileSystems.getDefault().newWatchService();
dir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
WatchKey key = watcher.take();
for (WatchEvent<?> e : key.pollEvents())
System.out.println(e.kind() + ": " + e.context());
key.reset();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}複数のディレクトリの監視
同じWatchServiceに複数のディレクトリを登録できます。返されたWatchKeyを使用して、イベントがどのディレクトリから発生したかを特定します。
Map<WatchKey, Path> keyMap = new HashMap<>();
for (Path dir : List.of(Path.of("/logs"), Path.of("/config"))) {
WatchKey key = dir.register(watcher,
StandardWatchEventKinds.ENTRY_MODIFY);
keyMap.put(key, dir);
}
WatchKey key = watcher.take();
Path dir = keyMap.get(key);
System.out.println("Changed in: " + dir);完全なパスの解決
イベントのcontext()が返すのはファイル名だけで、完全なパスではありません。監視対象のディレクトリを基準に解決してください。
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path filename = pathEvent.context();
Path fullPath = watchedDir.resolve(filename);
System.out.println("Full path: " + fullPath);ディレクトリの再帰的な監視
WatchServiceが監視するのは登録したディレクトリだけで、そのサブディレクトリは対象になりません。再帰的に監視するには、ツリーを走査して各サブディレクトリを登録します。
WatchService watcher = FileSystems.getDefault().newWatchService();
Path root = Path.of("/project");
try (var stream = Files.walk(root)) {
stream.filter(Files::isDirectory)
.forEach(dir -> {
try { dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
} catch (IOException e) { throw new RuntimeException(e); }
});
}リソースのクリーンアップ
OSリソースを解放するため、処理が終わったら必ずWatchServiceを閉じてください。
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
Path dir = Path.of("/tmp/watch");
dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
WatchKey key = watcher.poll(10, java.util.concurrent.TimeUnit.SECONDS);
if (key != null) {
key.pollEvents().forEach(e -> System.out.println(e.context()));
key.reset();
}
} // watcher.close() called automatically実践例:設定のホットリロード
ファイルが変更されたときに設定を再読み込みするために、WatchServiceを使用します。
class ConfigWatcher {
private volatile Map<String, String> config = new HashMap<>();
void loadConfig(Path file) throws IOException {
config = Files.readAllLines(file).stream()
.filter(l -> l.contains("="))
.collect(Collectors.toMap(
l -> l.split("=")[0].trim(),
l -> l.split("=")[1].trim(),
(a,b) -> b
));
}
// Call loadConfig again on ENTRY_MODIFY event
}WatchServiceの制限
重要な制限事項は次のとおりです。
- 個々のファイルではなく、ディレクトリのみを監視します
- デフォルトでは再帰的に監視しないため、各サブディレクトリを手動で登録する必要があります
- macOSでは内部的にポーリングを使用するため、Linuxのinotifyより応答性が低くなります
- 変更が短時間に大量に発生すると、見落とす場合があります(OVERFLOW)
- 監視対象のディレクトリが削除されると、WatchKeyは無効になります
確認問題
WatchKeyからイベントを処理した後、そのキーについてWatchServiceがさらにイベントを配信できるようにするには、何を呼び出す必要がありますか?
復習:WatchService
重要なポイント:
- ENTRY_CREATE、ENTRY_MODIFY、ENTRY_DELETEイベントを指定してディレクトリを登録します
- take()はブロックし、poll()はブロックせず、poll(timeout, unit)は時間を指定して待機します
- イベントの処理後は必ずkey.reset()を呼び出します
- context()が返すのはファイル名だけです。完全なパスを得るには監視対象のディレクトリを基準に解決します
- try-with-resourcesでWatchServiceを閉じます
- 再帰的な監視には各サブディレクトリの登録が必要です
よくある質問
「WatchService:ファイル変更の監視」レッスンは無料ですか?
はい。「WatchService:ファイル変更の監視」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「WatchService:ファイル変更の監視」で何を学びますか?
WatchService を登録し、ファイルの作成、変更、削除イベントをリアルタイムに検出します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「WatchService:ファイル変更の監視」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Path:ファイル場所の表現
- Files ユーティリティ:読み取り、書き込み、コピー、移動
- Files.walk によるディレクトリツリーの走査
- WatchService:ファイル変更の監視