Руководство по WatchService в Java NIO2

Руководство по WatchService в Java NIO2

1. обзор

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

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

import java.nio.file.*;

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

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

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

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

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

В основномwe can write code to poll the filesystem for changes on specific files and directories. Однако это решение не масштабируемо, особенно если файлы и каталоги достигают сотен и тысяч.

В Java 7 NIO.2 APIWatchService предоставляет масштабируемое решение для мониторинга каталогов на предмет изменений. Он имеет чистый 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. The StandardWatchEventKinds

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

  • StandardWatchEventKinds.ENTRY_CREATE - срабатывает, когда в наблюдаемом каталоге делается новая запись. Это может быть связано с созданием нового файла или переименованием существующего файла.

  • StandardWatchEventKinds.ENTRY_MODIFY - срабатывает при изменении существующей записи в наблюдаемом каталоге. Это событие запускается при редактировании всех файлов. На некоторых платформах даже изменение атрибутов файла вызовет это.

  • StandardWatchEventKinds.ENTRY_DELETE - срабатывает, когда запись удаляется, перемещается или переименовывается в наблюдаемом каталоге.

  • StandardWatchEventKinds.OVERFLOW - срабатывает, чтобы указать потерянные или отброшенные события. Мы не будем уделять этому много внимания

3.2. The WatchKey

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

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

Мы можем использовать APIpoll:

WatchKey watchKey = watchService.poll();

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

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

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

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

Наконец, мы можем использовать APItake:

WatchKey watchKey = watchService.take();

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

Здесь нужно отметить кое-что очень важное:when the WatchKey instance is returned by either of the poll or take APIs, it will not capture more events if it’s reset API is not invoked:

watchKey.reset();

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

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

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

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

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

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

4. Пример просмотра каталога

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

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

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

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();
        }
    }
}

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

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

Например, если вы перейдете на главную страницу пользователя, щелкните правой кнопкой мыши в пространстве, выберите `new – > file`, чтобы создать новый файл, а затем назовите егоtestFile. Затем вы добавляете контент и сохраняете. Вывод на консоли будет выглядеть так:

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.

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

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

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

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

И, как всегда, полный исходный код примеров, используемых в этой статье, доступен в папкеGithub project.