WatchSevice文件监听

lin 1260 字 463 次阅读


WatchSevice文件监听

一、前言

关于Java中的文件监听常见的有三种,他们分别是:

  1. 定时任务 + File.lastModified
  2. WatchService
  3. Apache Commons-IO

这三种文件监听的方式各有优劣,择需选择。

第一种:这个方案是最简单,最容易想到的方案。通过定时任务,轮训查询文件的最后修改时间,与上一次进行对比。如果发生变化,则说明文件已经修改,进行对应的业务逻辑处理。对于文件低频变动的场景,这种方案实现简单,基本上可以满足需求。不过Java 8和Java 9中File.lastModified似乎有Bug问题。

但该方案如果用在文件目录的变化上,缺点就有些明显了,比如:操作频繁,效率都损耗在遍历、保存状态、对比状态上了。

第二种:在Java 7中新增了java.nio.file.WatchService,通过它可以实现文件变动的监听。WatchService是基于操作系统的文件系统监控器,可以监控系统所有文件的变化,无需遍历、无需比较,是一种基于信号收发的监控,效率高。

相对于第一种,实现起来简单,效率高。不足的是,只能监听当前目录下的文件和目录,不能监视子目录,而且我们也看到监听只能算是准实时的,而且监听时间只能取API默认提供的三个值。

第三种:Apache Commons IO 提供了一个强大的工具包,可以用于监控文件和目录的变化。通过使用 FileAlterationMonitorFileAlterationObserverFileAlterationListener,我们可以轻松地实现对文件系统的监控。

二、Watchservice 如何工作?

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

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

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

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

在此步骤之后,我们需要向监视服务注册路径。在这个阶段,有两个重要的概念需要理解。StandardWatchEventKinds 类和 WatchKey 类。

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

2.1 StandardWatchEventKinds

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

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

2.2 WatchKey

此类为向监视服务注册目录。当我们注册一个目录时,它的实例由监视服务返回给我们,当我们询问监视服务是否发生了我们注册的任何事件时。
监视服务不为我们提供每当事件发生时调用的回调方法。我们只能通过多种方式进行轮询以找到此信息。

列如:

WatchKey watchKey = watchService.poll();

此方法调用后立即返回,如果没有注册的事件发生则返回 null。可设置timeout参数,当没有事件发生时会等待超时,而不是立即返回null

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

使用 take() 方法会一直阻塞,直到有事件发生。

WatchKey watchKey = watchService.take();

需要重点说明的是:WatchKey实例 由 poll 和 take 返回后,我们需要reset watchKey,否者无法捕获更多事件

即:当 WatchKey 实例由轮询或接受API 返回时,如果不调用重置 API,它将不会捕获更多事件

watchKey.reset();

这意味着每次通过 poll 操作返回 WatchKey 实例时,它会从 watch service 队列中被移除。reset方法 调用将其放回队列中等待更多事件。

所以通常我们需要在一个循环中不断检查被监控目录的变化并进行相应处理:

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

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

当获得 WatchKey 时,while 循环会执行其中的代码。我们使用 WatchKey.pollEvents 返回发生的事件列表,然后使用 for each 循环逐个处理事件。

在处理完所有事件之后,我们必须调用 reset 来重新将 WatchKey 入队。

三、一个示例

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

四、结语

本文中简要介绍了watchservice文件监听的用法,

天下繁华,唯有一心
最后更新于 2025-12-11