«1. Обзор

В этой статье мы собираемся изучить интерфейс WatchService API файловой системы Java NIO.2. Это одна из менее известных функций новых API-интерфейсов ввода-вывода, представленных в Java 7 вместе с интерфейсом FileVisitor.

Чтобы использовать интерфейс WatchService в своих приложениях, вам необходимо импортировать соответствующие классы:

import java.nio.file.*;

2. Зачем использовать WatchService

Распространенным примером для понимания того, что делает служба, является среда IDE.

Вы могли заметить, что IDE всегда обнаруживает изменения в файлах исходного кода, которые происходят вне ее. Некоторые IDE информируют вас с помощью диалогового окна, чтобы вы могли выбрать, перезагружать файл из файловой системы или нет, другие просто обновляют файл в фоновом режиме.

Точно так же более новые фреймворки, такие как Play, также по умолчанию выполняют горячую перезагрузку кода приложения — всякий раз, когда вы выполняете редактирование из любого редактора.

Эти приложения используют функцию уведомления об изменении файла, которая доступна во всех файловых системах.

По сути, мы можем написать код для опроса файловой системы на наличие изменений в определенных файлах и каталогах. Однако это решение не масштабируется, особенно если количество файлов и каталогов достигает сотен и тысяч.

В Java 7 NIO.2 API WatchService предоставляет масштабируемое решение для мониторинга каталогов на наличие изменений. Он имеет чистый API и настолько хорошо оптимизирован для производительности, что нам не нужно реализовывать собственное решение.

3. Как работает Watchservice?

Чтобы использовать функции WatchService, первым шагом является создание экземпляра WatchService с использованием класса java.nio.file.FileSystems:

WatchService watchService = FileSystems.getDefault().newWatchService();

Затем мы должны создать путь к каталогу, который мы хотим отслеживать. :

Path path = Paths.get("pathToDir");

После этого шага мы должны зарегистрировать путь в сервисе часов. На этом этапе необходимо понять две важные концепции. Класс StandardWatchEventKinds и класс WatchKey. Взгляните на следующий регистрационный код, чтобы понять, где каждое падение. Мы последуем этому с объяснениями того же:

WatchKey watchKey = path.register(
  watchService, StandardWatchEventKinds...);

Обратите внимание только на две важные вещи здесь: Во-первых, вызов API регистрации пути принимает экземпляр службы наблюдения в качестве первого параметра, за которым следуют переменные аргументы StandardWatchEventKinds. Во-вторых, возвращаемый тип процесса регистрации — экземпляр WatchKey.

3.1. StandardWatchEventKinds

Это класс, экземпляры которого сообщают службе наблюдения, какие виды событий следует отслеживать в зарегистрированном каталоге. В настоящее время существует четыре возможных события, за которыми следует следить:

    StandardWatchEventKinds.ENTRY_CREATE — срабатывает, когда в отслеживаемом каталоге создается новая запись. Это может быть связано с созданием нового файла или переименованием существующего файла. StandardWatchEventKinds.ENTRY_MODIFY — срабатывает при изменении существующей записи в отслеживаемом каталоге. Все изменения файлов вызывают это событие. На некоторых платформах это вызывает даже изменение атрибутов файла. StandardWatchEventKinds.ENTRY_DELETE — срабатывает, когда запись удаляется, перемещается или переименовывается в отслеживаемом каталоге. StandardWatchEventKinds.OVERFLOW — срабатывает для индикации потерянных или отброшенных событий. Мы не будем уделять этому много внимания

3.2. WatchKey

Этот класс представляет регистрацию каталога в службе наблюдения. Его экземпляр возвращается нам службой наблюдения, когда мы регистрируем каталог и когда мы спрашиваем службу наблюдения, произошли ли какие-либо события, для которых мы зарегистрировались.

Сервис Watch не предлагает нам методов обратного вызова, которые вызываются всякий раз, когда происходит событие. Мы можем только опросить его несколькими способами, чтобы найти эту информацию.

Мы можем использовать API-интерфейс опроса:

WatchKey watchKey = watchService.poll();

Этот вызов API возвращается сразу же. Он возвращает следующий ключ наблюдения в очереди, любое из событий которого произошло, или null, если зарегистрированных событий не произошло.

Мы также можем использовать перегруженную версию, которая принимает аргумент тайм-аута:

WatchKey watchKey = watchService.poll(long timeout, TimeUnit units);

«

«Этот вызов API похож на предыдущий по возвращаемому значению. Однако он блокирует единицы времени тайм-аута, чтобы дать больше времени, в течение которого может произойти событие, вместо того, чтобы сразу возвращать значение null.

WatchKey watchKey = watchService.take();

Наконец, мы можем использовать API-интерфейс take:

Этот последний подход просто блокирует до тех пор, пока не произойдет событие.

watchKey.reset();

Здесь мы должны отметить очень важную вещь: когда экземпляр WatchKey возвращается API опроса или взятия, он не будет захватывать больше событий, если его API сброса не вызывается:

Это означает, что Экземпляр ключа наблюдения удаляется из очереди службы наблюдения каждый раз, когда он возвращается операцией опроса. Вызов API сброса возвращает его в очередь для ожидания дополнительных событий.

WatchKey key;
while ((key = watchService.take()) != null) {
    for (WatchEvent<?> event : key.pollEvents()) {
        //process
    }
    key.reset();
}

Для наиболее практического применения службы наблюдателя потребовался бы цикл, в котором мы постоянно проверяем изменения в отслеживаемом каталоге и обрабатываем их соответствующим образом. Для реализации этого можно использовать следующую идиому:

Мы создаем ключ наблюдения для хранения возвращаемого значения операции опроса. Цикл while будет блокироваться до тех пор, пока условный оператор не вернется либо с ключом наблюдения, либо с нулевым значением.

Когда мы получаем ключ watch, цикл while выполняет код внутри него. Мы используем API WatchKey.pollEvents для возврата списка произошедших событий. Затем мы используем цикл for each для обработки их один за другим.

После того, как все события обработаны, мы должны вызвать API сброса, чтобы снова поставить ключ наблюдения в очередь.

4. Пример наблюдения за каталогом

Поскольку в предыдущем подразделе мы рассмотрели API WatchService, его внутреннюю работу и то, как мы можем его использовать, теперь мы можем перейти к полному практическому примеру.

Из соображений переносимости мы будем следить за активностью в домашнем каталоге пользователя, который должен быть доступен во всех современных операционных системах.

public class DirectoryWatcherExample {

    public static void main(String[] args) {
        WatchService watchService
          = FileSystems.getDefault().newWatchService();

        Path path = Paths.get(System.getProperty("user.home"));

        path.register(
          watchService, 
            StandardWatchEventKinds.ENTRY_CREATE, 
              StandardWatchEventKinds.ENTRY_DELETE, 
                StandardWatchEventKinds.ENTRY_MODIFY);

        WatchKey key;
        while ((key = watchService.take()) != null) {
            for (WatchEvent<?> event : key.pollEvents()) {
                System.out.println(
                  "Event kind:" + event.kind() 
                    + ". File affected: " + event.context() + ".");
            }
            key.reset();
        }
    }
}

Код содержит всего несколько строк кода, поэтому мы просто сохраним его в основном методе:

И это все, что нам действительно нужно сделать. Теперь вы можете запустить класс, чтобы начать просмотр каталога.

Когда вы переходите в домашний каталог пользователя и выполняете какие-либо действия с файлами, такие как создание файла или каталога, изменение содержимого файла или даже удаление файла, все это будет зарегистрировано на консоли.

Event kind:ENTRY_CREATE. File affected: New Text Document.txt.
Event kind:ENTRY_DELETE. File affected: New Text Document.txt.
Event kind:ENTRY_CREATE. File affected: testFile.txt.
Event kind:ENTRY_MODIFY. File affected: testFile.txt.
Event kind:ENTRY_MODIFY. File affected: testFile.txt.

Например, предположим, что вы заходите в домашнюю папку пользователя, щелкните правой кнопкой мыши в пространстве, выберите «новый — \u003e файл», чтобы создать новый файл, а затем назовите его testFile. Затем вы добавляете контент и сохраняете. Вывод в консоли будет выглядеть так:

Не стесняйтесь редактировать путь, чтобы он указывал на любой каталог, который вы хотите просмотреть.

5. Заключение

В этой статье мы рассмотрели некоторые из менее часто используемых функций, доступных в Java 7 NIO.2 — API файловой системы, в частности интерфейс WatchService.

Нам также удалось пройти этапы создания приложения для просмотра каталогов, чтобы продемонстрировать его функциональность.