Documentation

The Java™ Tutorials
Hide TOC
Watching a Directory for Changes监视目录中的更改
Trail: Essential Java Classes
Lesson: Basic I/O
Section: File I/O (Featuring NIO.2)

Watching a Directory for Changes监视目录中的更改

Have you ever found yourself editing a file, using an IDE or another editor, and a dialog box appears to inform you that one of the open files has changed on the file system and needs to be reloaded? 您是否曾经发现自己在使用IDE或其他编辑器编辑文件时,会出现一个对话框,通知您文件系统中打开的一个文件已更改,需要重新加载?Or perhaps, like the NetBeans IDE, the application just quietly updates the file without notifying you. 或者,像NetBeans IDE一样,应用程序只是悄悄地更新文件而不通知您。The following sample dialog box shows how this notification looks with the free editor, jEdit:以下示例对话框显示了此通知在自由编辑器jEdit中的外观:

Sample jEdit Dialog stating: The following files were changed on disk by another program.

jEdit Dialog Box Showing That a Modified File Is Detected显示检测到修改文件的jEdit对话框

To implement this functionality, called file change notification, a program must be able to detect what is happening to the relevant directory on the file system. 要实现此功能(称为文件更改通知),程序必须能够检测文件系统上的相关目录发生了什么。One way to do so is to poll the file system looking for changes, but this approach is inefficient. 一种方法是轮询文件系统以查找更改,但这种方法效率低下。It does not scale to applications that have hundreds of open files or directories to monitor.它不能扩展到有数百个打开的文件或目录要监视的应用程序。

The java.nio.file package provides a file change notification API, called the Watch Service API. java.nio.file包提供了一个文件更改通知API,称为监视服务API。This API enables you to register a directory (or directories) with the watch service. 此API允许您向监视服务注册一个或多个目录。When registering, you tell the service which types of events you are interested in: file creation, file deletion, or file modification. 注册时,您会告诉服务您感兴趣的事件类型:文件创建、文件删除或文件修改。When the service detects an event of interest, it is forwarded to the registered process. 当服务检测到感兴趣的事件时,它被转发到已注册的进程。The registered process has a thread (or a pool of threads) dedicated to watching for any events it has registered for. 注册的进程有一个线程(或线程池),专门用于监视它注册的任何事件。When an event comes in, it is handled as needed.当事件发生时,会根据需要进行处理。

This section covers the following:本节包括以下内容:

Watch Service Overview监视服务概述

The WatchService API is fairly low level, allowing you to customize it. WatchService API的级别相当低,允许您对其进行自定义。You can use it as is, or you can choose to create a high-level API on top of this mechanism so that it is suited to your particular needs.您可以按原样使用它,也可以选择在这个机制之上创建一个高级API,以便它适合您的特定需求。

Here are the basic steps required to implement a watch service:以下是实现监视服务所需的基本步骤:

WatchKeys are thread-safe and can be used with the java.nio.concurrent package. WatchKeys是线程安全的,可以与java.nio.concurrent包一起使用。You can dedicate a thread pool to this effort.您可以将一个线程池专门用于此工作。

Try It Out试试看

Because this API is more advanced, try it out before proceeding. 由于此API更高级,请在继续之前试用它。Save the WatchDir example to your computer, and compile it. WatchDir示例保存到您的计算机中,并对其进行编译。Create a test directory that will be passed to the example. 创建将传递给示例的test目录。WatchDir uses a single thread to process all events, so it blocks keyboard input while waiting for events. WatchDir使用单个线程处理所有事件,因此它在等待事件时阻止键盘输入。Either run the program in a separate window, or in the background, as follows:在单独的窗口或后台运行程序,如下所示:

java WatchDir test &

Play with creating, deleting, and editing files in the test directory. test目录中创建、删除和编辑文件。When any of these events occurs, a message is printed to the console. 当这些事件中的任何一个发生时,将向控制台打印一条消息。When you have finished, delete the test directory and WatchDir exits. 完成后,删除test目录并退出WatchDirOr, if you prefer, you can manually kill the process.或者,如果愿意,可以手动终止该进程。

You can also watch an entire file tree by specifying the -r option. 您还可以通过指定-r选项来查看整个文件树。When you specify -r, WatchDir walks the file tree, registering each directory with the watch service.指定-r时,WatchDir遍历文件树,向监视服务注册每个目录。

Creating a Watch Service and Registering for Events创建监视服务并注册事件

The first step is to create a new WatchService by using the newWatchService method in the FileSystem class, as follows:第一步是使用FileSystem类中的newWatchService方法创建一个新的WatchService,如下所示:

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

Next, register one or more objects with the watch service. 接下来,向监视服务注册一个或多个对象。Any object that implements the Watchable interface can be registered. 任何实现Watchable接口的对象都可以注册。The Path class implements the Watchable interface, so each directory to be monitored is registered as a Path object.Path类实现了Watchable接口,因此要监视的每个目录都注册为Path对象。

As with any Watchable, the Path class implements two register methods. 与任何可观察对象一样,Path类实现两个register方法。This page uses the two-argument version, register(WatchService, WatchEvent.Kind<?>...). 此页面使用两个参数版本register(WatchService, WatchEvent.Kind<?>...)(The three-argument version takes a WatchEvent.Modifier, which is not currently implemented.)(三参数版本采用WatchEvent.Modifier,该修饰符当前未实现。)

When registering an object with the watch service, you specify the types of events that you want to monitor. 向watch服务注册对象时,指定要监视的事件类型。The supported StandardWatchEventKinds event types follow:支持的StandardWatchEventKinds事件类型如下:

The following code snippet shows how to register a Path instance for all three event types:以下代码段显示了如何为所有三种事件类型注册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处理事件

The order of events in an event processing loop follow:事件处理循环中的事件顺序如下:

  1. Get a watch key. 获得一个监视密钥。Three methods are provided:提供了三种方法:
    • pollReturns a queued key, if available. 返回排队的密钥(如果可用)。Returns immediately with a null value, if unavailable.如果不可用,立即返回null值。
    • poll(long, TimeUnit)Returns a queued key, if one is available. 返回排队的密钥(如果有)。If a queued key is not immediately available, the program waits until the specified time. 如果排队的密钥不立即可用,程序将等待到指定的时间。The TimeUnit argument determines whether the specified time is nanoseconds, milliseconds, or some other unit of time.TimeUnit参数确定指定的时间是纳秒、毫秒还是其他时间单位。
    • takeReturns a queued key. 返回排队的密钥。If no queued key is available, this method waits.如果没有可用的排队密钥,此方法将等待。
  2. Process the pending events for the key. 处理密钥的挂起事件。You fetch the List of WatchEvents from the pollEvents method.您可以从pollEvents方法获取WatchEventsList
  3. Retrieve the type of event by using the kind method. 使用kind方法检索事件的类型。No matter what events the key has registered for, it is possible to receive an OVERFLOW event. 无论密钥注册了什么事件,都有可能接收OVERFLOW事件。You can choose to handle the overflow or ignore it, but you should test for it.您可以选择处理溢出或忽略它,但您应该测试它。
  4. Retrieve the file name associated with the event. 检索与事件关联的文件名。The file name is stored as the context of the event, so the context method is used to retrieve it.文件名存储为事件的上下文,因此使用context方法检索它。
  5. After the events for the key have been processed, you need to put the key back into a ready state by invoking reset. 处理完密钥的事件后,需要通过调用reset将密钥恢复到ready状态。If this method returns false, the key is no longer valid and the loop can exit. 如果此方法返回false,则键不再有效,循环可以退出。This step is very important. 这一步非常重要If you fail to invoke reset, this key will not receive any further events.如果调用reset失败,此键将不会接收任何其他事件。

A watch key has a state. 监视密钥有一个状态。At any given time, its state might be one of the following:在任何给定时间,其状态可能为以下状态之一:

Here is an example of an event processing loop. 下面是一个事件处理循环的示例。It is taken from the Email example, which watches a directory, waiting for new files to appear. 它取自Email示例,该示例监视一个目录,等待新文件出现。When a new file becomes available, it is examined to determine if it is a text/plain file by using the probeContentType(Path) method. 当新文件可用时,将使用probeContentType(Path)方法对其进行检查,以确定它是否为文本/普通文件。The intention is that text/plain files will be emailed to an alias, but that implementation detail is left to the reader.其目的是将text/plain文件通过电子邮件发送给别名,但实现细节留给读者。

The methods specific to the watch service API are shown in bold:特定于监视服务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;
    }
}

Retrieving the File Name检索文件名

The file name is retrieved from the event context. 从事件上下文中检索文件名。The Email example retrieves the file name with this code:Email示例使用以下代码检索文件名:

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

When you compile the Email example, it generates the following error:编译Email示例时,会生成以下错误:

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

This error is a result of the line of code that casts the WatchEvent<T> to a WatchEvent<Path>. 此错误是将WatchEvent<T>强制转换为WatchEvent<Path>结果。The WatchDir example avoids this error by creating a utility cast method that suppresses the unchecked warning, as follows:WatchDir示例通过创建抑制未检查警告的实用程序强制转换方法来避免此错误,如下所示:

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

If you are unfamiliar with the @SuppressWarnings syntax, see Annotations.如果您不熟悉@SuppressWarnings语法,请参阅注释

When to Use and Not Use This API何时使用和不使用此API

The Watch Service API is designed for applications that need to be notified about file change events. 监视服务API是为需要通知文件更改事件的应用程序而设计的。It is well suited for any application, like an editor or IDE, that potentially has many open files and needs to ensure that the files are synchronized with the file system. 它非常适合任何应用程序,如编辑器或IDE,这些应用程序可能有许多打开的文件,并且需要确保这些文件与文件系统同步。It is also well suited for an application server that watches a directory, perhaps waiting for .jsp or .jar files to drop, in order to deploy them.它也非常适合监视目录的应用程序服务器,可能是等待.jsp.jar文件删除,以便部署它们。

This API is not designed for indexing a hard drive. 此API不是为索引硬盘驱动器而设计的。Most file system implementations have native support for file change notification. 大多数文件系统实现都具有对文件更改通知的本机支持。The Watch Service API takes advantage of this support where available. 监视服务API在可用的情况下利用了这种支持。However, when a file system does not support this mechanism, the Watch Service will poll the file system, waiting for events.但是,当文件系统不支持此机制时,监视服务将轮询文件系统,等待事件发生。


Previous page: Finding Files
Next page: Other Useful Methods