1

NIO 之 WatchService

Java 1.6版本以前是不存在目录监控的API的。如果要实现这种功能必须要自己遍历目录,记录各个文件的情况,然后定时全部遍历一次,从 JDK7 之后出现了 WatchService 类,实现了对目录下文件的监控。

整体流程

整个监控目录文件操作的流程大致如下:

  1. 获取 WatchService
  2. 注册指定目录的监视器 WatchService
  3. 等待目录下的文件发生变化
  4. 对发生变化的文件进行操作

获取 WatchService 实例

WatchService 类的实现实际上是对操作系统的文件监视器的封装,相比之前的手动实现,优雅了不少。因为不需要遍历文件整体而言效率也高很多。以下为获取 WatchService 实例的代码,通过 FileSystem.getDefault() 可看出并非是自己实现的。从 newWatchService() 方法名看, WatchService 可以获取多个。

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

实际上调用此方法后,程序会新开一个线程,监视文件变化发出的信号,此时线程尚未就绪。

为目录注册监视器

有了监视器,接下来我们需要注册监视器了。

Path path = Paths.get("src");
WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

注册监视器需要用到 Path 实例,该实例对应的必须是一个目录,不允许是一个文件。
方法比较简单,就是说为目录注册一个监视器,监视目录下文件的变化。
关于 StandardWatchEventKinds.ENTRY_MODIFY ,表示监视文件的修改事件,它是 WatchEvent.Kind<?> 的实现类。
看起来它像是枚举,实际上它并不是。JDK 中帮我们定义了三种事件,新增、修改和删除。

获取目录下的变化

获取目录的变化需要使用 WatchServicetake() 方法或 poll() 方法。

WatchKey key = watchService.take();
WatchKey pollKey = watchService.poll();

take() 是一个阻塞方法,会等待监视器发出的信号才返回。
poll() 是一个非阻塞方法,会立即返回当时监视器中是否有信号。
返回的 WatchKey 对象,实际上是一个单例,和之前 path.register() 方法返回的实例是同一个。它只能保存某一时间点的文件变化信息。

处理文件变化事件

List<WatchEvent<?>> events = key.pollEvents();
for (WatchEvent<?> pollEvent : events) {
    Object o = pollEvent.context();
    WatchEvent.Kind kind = pollEvent.kind();
}

key.reset();

pollEvents() 用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。
context() 返回触发该事件的那个文件或目录的路径(相对路径)
kind() 返回事件类型(ENTRY_CREATE、ENTRY_DELETE、ENTRY_MODIFY之一)
reset() 每次调用 WatchService 的 take() 或 poll() 方法时需要通过本方法重置。

一个简单的例子

public void watchServiceExample() throws IOException, InterruptedException {
    WatchService watchService = FileSystems.getDefault().newWatchService();

    Path path = Paths.get("D:/code");

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

    while (true) {
        // 尝试获取下一个变化信息的监控池,如果没有变化则一直等待
        WatchKey key = watchService.take();

        for (WatchEvent<?> pollEvent : key.pollEvents()) {
            System.out.println(String.format("%s is %s.", pollEvent.context().toString(), pollEvent.kind().name().substring(6)));
        }

        if (!key.reset()) {
            break;
        }
    }
}

总结

WatchService 的优点就不用多说了,这里就说一个缺点: 只能监视当前目录下的文件和目录,不能监视子目录


参考

[疯狂Java]NIO.2:WatchService、WatchKey(监控文件变化)

本文如有问题,欢迎在评论区中指正。


魏晋秋
47 声望2 粉丝

勿惧勿怕