Java NIO 2中的WatchService指南

2023/05/29

1. 概述

在本文中,我们将探讨Java NIO.2文件系统API的WatchService接口。这是Java 7中与FileVisitor接口一起引入的较新IO API中鲜为人知的功能之一。

要在你的应用程序中使用WatchService接口,你需要导入适当的类:

import java.nio.file.*;

2. 为什么要使用WatchService

了解服务功能的一个常见示例实际上是IDE。

你可能已经注意到,IDE始终会检测到发生在其外部的源代码文件中的更改。某些IDE使用对话框通知你,以便你可以选择是否从文件系统重新加载文件,而某些IDE只是在后台更新文件。

同样,较新的框架(如Play)也会默认热重载应用程序代码-无论何时你从任何编辑器执行编辑。

这些应用程序采用了一种称为文件更改通知的功能,该功能在所有文件系统中都可用。

基本上,我们可以编写代码来轮询文件系统以了解特定文件和目录的更改。但是,此解决方案不可扩展,尤其是当文件和目录达到成百上千时。

在Java 7 NIO.2中,WatchService API提供了一个可扩展的解决方案来监控目录的变化。它有一个干净的API,并且针对性能进行了很好的优化,我们不需要实现我们自己的解决方案。

3. WatchService是如何工作的?

要使用WatchService功能,第一步是使用java.nio.file.FileSystems类创建一个WatchService实例:

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

接下来,我们必须创建要监视的目录的路径:

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

完成此步骤后,我们必须向watchService注册路径。在这个阶段有两个重要的概念需要理解。StandardWatchEventKinds类和WatchKey类。看看下面的注册代码,就可以了解它们的用法。我们将对此进行解释:

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

这里只注意两件重要的事情:首先,路径register API调用将WatchService实例作为第一个参数,后跟StandardWatchEventKinds的可变参数。其次,注册过程的返回类型是WatchKey实例。

3.1 StandardWatchEventKinds

这是一个类,其实例告诉WatchService要在注册目录上监视的事件类型。目前有四种可能的事件需要关注:

  • StandardWatchEventKinds.ENTRY_CREATE:在监视目录中创建新条目时触发,这可能是由于创建新文件或重命名现有文件所致。
  • StandardWatchEventKinds.ENTRY_MODIFY:当监视目录中的现有条目被修改时触发,所有文件编辑都会触发此事件。在某些平台上,即使更改文件属性也会触发它。
  • StandardWatchEventKinds.ENTRY_DELETE:在监视目录中删除、移动或重命名条目时触发。
  • StandardWatchEventKinds.OVERFLOW:触发以指示丢失或丢弃的事件,我们不会过多关注它。

3.2 WatchKey

此类表示向WatchService注册目录。当我们注册目录时以及当我们询问WatchService是否发生了我们注册的任何事件时,WatchService会将其实例返回给我们。

WatchService没有为我们提供任何事件发生时调用的回调方法。我们只能通过多种方式轮询它来找到这些信息。

我们可以使用poll API:

WatchKey watchKey = watchService.poll();

此API调用立即返回。它返回下一个排队的WatchKey,其任何事件已发生,如果没有注册事件发生则返回null。

我们还可以使用带有超时参数的重载版本:

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

此API调用在返回值方面与上一个类似。但是,它会阻塞超时时间单位,以提供更多事件可能发生的时间,而不是立即返回null。

最后,我们可以使用take API:

WatchKey watchKey = watchService.take();

最后一种方法只是阻塞,直到事件发生。

这里我们必须注意一些非常重要的事情:当WatchKey实例由poll或take API返回时,如果不调用reset API,它将不会捕获更多事件

watchKey.reset();

这意味着每次轮询操作返回WatchKey实例时,它都会从WatchService队列中删除。reset API调用将其放回队列中以等待更多事件。

WatchService最实际的应用需要一个循环,在这个循环中我们不断地检查被监视目录的变化并进行相应的处理。我们可以使用以下伪代码来实现这一点:

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

我们创建一个WatchKey来存储轮询操作的返回值。while循环将阻塞,直到条件语句返回WatchKey或null。

当我们得到一个WatchKey时,while循环就会执行其中的代码。我们使用WatchKey.pollEvents API返回已发生事件的列表。然后我们使用foreach循环来逐个处理它们。

所有事件处理完毕后,我们必须调用reset API重新入队WatchKey。

4. 目录监视示例

由于我们在上一小节中介绍了WatchService API及其内部工作方式以及如何使用它,我们现在可以继续查看一个完整且实用的示例。

出于可移植性原因,我们将监视用户主目录中的活动,该目录应该在所有现代操作系统上都可用。

该示例仅包含几行代码,因此我们将其保留在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. 总结

在本文中,我们探讨了Java 7 NIO.2文件系统API中一些不太常用的功能,尤其是WatchService接口。

我们还设法通过构建目录监视应用程序来演示功能的步骤。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章