WatchService: отслеживание изменений файлов
Регистрируйте WatchService для обнаружения событий создания, изменения и удаления файлов в реальном времени.
«WatchService: отслеживание изменений файлов» — бесплатный урок Java Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Что такое WatchService
WatchService отслеживает события файловой системы в каталоге: создание, изменение и удаление. Он использует уведомления на уровне ОС (inotify в Linux, FSEvents в macOS), что значительно эффективнее опроса.
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); }
});
}Освобождение ресурсов
После завершения всегда закрывайте 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 внутри используется опрос (он менее отзывчив, чем inotify в Linux)
- При быстро следующих друг за другом изменениях часть событий может быть пропущена (
OVERFLOW) WatchKeyстановится недействительным, если отслеживаемый каталог удалить
Быстрая проверка
После обработки событий из WatchKey какой метод необходимо вызвать, прежде чем WatchService сможет передать следующие события для этого ключа?
Итоги: WatchService
Основные выводы:
- Регистрируйте каталоги для событий ENTRY_CREATE/MODIFY/DELETE
take()блокирует выполнение;poll()не блокирует; используйтеpoll(timeout, unit)для ожидания в течение заданного времени- Всегда вызывайте
key.reset()после обработки событий context()возвращает только имя файла — для получения полного пути разрешите его относительно отслеживаемого каталога- Закрывайте
WatchServiceс помощью конструкции try-with-resources - Для рекурсивного отслеживания необходимо зарегистрировать каждый подкаталог
Часто задаваемые вопросы
Урок «WatchService: отслеживание изменений файлов» бесплатный?
Да — полный текст урока «WatchService: отслеживание изменений файлов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «WatchService: отслеживание изменений файлов»?
Регистрируйте WatchService для обнаружения событий создания, изменения и удаления файлов в реальном времени. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «WatchService: отслеживание изменений файлов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Path: представление расположений файлов
- Утилита Files: чтение, запись, копирование и перемещение
- Обход деревьев каталогов с Files.walk
- WatchService: отслеживание изменений файлов