0Pricing
Java Academy · Урок

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 — локальная установка не требуется.

Все уроки этого курса

  1. Path: представление расположений файлов
  2. Утилита Files: чтение, запись, копирование и перемещение
  3. Обход деревьев каталогов с Files.walk
  4. WatchService: отслеживание изменений файлов
← Назад к Java Academy