监视目录以进行更改

您是否曾经发现自己使用 IDE 或其他编辑器编辑文件,并且出现一个对话框来通知您文件系统中一个打开的文件已更改,需要重新加载?也许像 NetBeans IDE 一样,该应用程序只是在不通知您的情况下静默更新文件。以下示例对话框显示了使用免费编辑器jEdit时此通知的外观:

jEdit 对话框显示检测到修改后的文件

为了实现称为“文件更改通知”的此功能,程序必须能够检测文件系统上相关目录正在发生的事情。一种方法是轮询文件系统以查找更改,但是这种方法效率低下。它不能扩展到具有数百个打开的文件或目录要监视的应用程序。

java.nio.file软件包提供了文件更改通知 API,称为监视服务 API。使用此 API,您可以通过监视服务注册一个或多个目录。注册时,您可以告诉服务您感兴趣的事件类型:文件创建,文件删除或文件修改。当服务检测到感兴趣的事件时,它将转发到注册的进程。已注册的进程有一个线程(或线程池)专用于监视其已注册的任何事件。当事件进入时,将根据需要进行处理。

本节包括以下内容:

手表服务概述

WatchService API 的级别较低,您可以对其进行自定义。您可以按原样使用它,也可以选择在此机制之上创建一个高级 API,使其适合您的特定需求。

以下是实现监视服务所需的基本步骤:

WatchKeys是线程安全的,可以与java.nio.concurrent包一起使用。您可以为此投入thread pool

试用

由于此 API 更高级,请在 continue 之前try一下。将WatchDir示例保存到您的计算机上并进行编译。创建一个test目录,该目录将传递给示例。 WatchDir使用单个线程来处理所有事件,因此它在 await 事件时会阻止键盘 Importing。在单独的窗口中或在后台运行程序,如下所示:

java WatchDir test &

播放在test目录中创建,删除和编辑文件的过程。这些事件中的任何一个发生时,都会在控制台上显示一条消息。完成后,删除test目录,然后WatchDir退出。或者,如果您愿意,可以手动终止该过程。

您还可以通过指定-r选项来监视整个文件树。当您指定-rWatchDir 走文件树时,请在监视服务中注册每个目录。

创建监视服务并注册事件

第一步是使用FileSystem类中的newWatchService方法创建一个新的WatchService,如下所示:

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

接下来,向监视服务注册一个或多个对象。可以注册实现Watchableinterface的任何对象。 Path类实现Watchableinterface,因此要监视的每个目录都注册为Path对象。

与任何Watchable一样,Path类实现两个register方法。此页面使用两个参数的版本register(WatchService, WatchEvent.Kind<?>...)。 (三参数版本采用WatchEvent.Modifier,当前未实现.)

在监视服务中注册对象时,可以指定要监视的事件类型。受支持的StandardWatchEventKinds事件类型如下:

以下代码段显示了如何为所有三种事件类型注册Path实例:

import static java.nio.file.StandardWatchEventKinds.*;

Path dir = ...;
try {
    WatchKey key = dir.register(watcher,
                           ENTRY_CREATE,
                           ENTRY_DELETE,
                           ENTRY_MODIFY);
} catch (IOException x) {
    System.err.println(x);
}

Processing Events

事件处理循环中事件的 Sequences 如下:

监视键具有状态。在任何给定时间,其状态可能是以下之一:

这是事件处理循环的示例。它取自Email示例,该示例监视目录,await 新文件出现。当有新文件可用时,将使用probeContentType(Path)方法检查它是否为text/plain文件。Object 是将text/plain文件通过电子邮件发送给别名,但实现细节留给 Reader。

监视服务 API 特有的方法以粗体显示:

for (;;) {

    // wait for key to be signaled
    WatchKey key;
    try {
        key = watcher.take();
    } catch (InterruptedException x) {
        return;
    }

    for (WatchEvent<?> event: key.pollEvents()) {
        WatchEvent.Kind<?> kind = event.kind();

        // This key is registered only
        // for ENTRY_CREATE events,
        // but an OVERFLOW event can
        // occur regardless if events
        // are lost or discarded.
        if (kind == OVERFLOW) {
            continue;
        }

        // The filename is the
        // context of the event.
        WatchEvent<Path> ev = (WatchEvent<Path>)event;
        Path filename = ev.context();

        // Verify that the new
        //  file is a text file.
        try {
            // Resolve the filename against the directory.
            // If the filename is "test" and the directory is "foo",
            // the resolved name is "test/foo".
            Path child = dir.resolve(filename);
            if (!Files.probeContentType(child).equals("text/plain")) {
                System.err.format("New file '%s'" +
                    " is not a plain text file.%n", filename);
                continue;
            }
        } catch (IOException x) {
            System.err.println(x);
            continue;
        }

        // Email the file to the
        //  specified email alias.
        System.out.format("Emailing file %s%n", filename);
        //Details left to reader....
    }

    // Reset the key -- this step is critical if you want to
    // receive further watch events.  If the key is no longer valid,
    // the directory is inaccessible so exit the loop.
    boolean valid = key.reset();
    if (!valid) {
        break;
    }
}

检索文件名

从事件上下文中检索文件名。 Email示例使用以下代码检索文件名:

WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();

当您编译Email示例时,它会产生以下错误:

Note: Email.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

此错误是代码行将WatchEvent<T>强制转换为WatchEvent<Path>的结果。 WatchDir示例通过创建 Utilcast方法来避免未检查的警告,从而避免了该错误,如下所示:

@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
    return (WatchEvent<Path>)event;
}

如果您不熟悉@SuppressWarnings语法,请参见Annotations

何时使用和不使用此 API

Watch Service API 专为需要通知文件更改事件的应用程序而设计。它非常适合任何可能具有许多打开文件且需要确保文件与文件系统同步的应用程序,例如编辑器或 IDE。它也非常适合监视目录的应用程序服务器,该目录可能 await.jsp.jar文件删除以进行部署。

该 API 不是设计用于索引硬盘驱动器。大多数文件系统实现均具有文件更改通知的本机支持。 Watch Service API 在可用的情况下利用此支持。但是,当文件系统不支持此机制时,监视服务将轮询文件系统,以 await 事件。

首页