java.nio.file.WatchService 监控文件变化
志波同学 人气:0在平时的开发过程中,会有很多场景需要实时监听文件的变化,如下:
1、通过实时监控 mysql 的 binlog 日志实现数据同步
2、修改配置文件后,希望系统可以实时感知
3、应用系统将日志写入文件中,日志监控系统可以实时抓取日志,分析日志内容并进行报警
4、类似 ide 工具,可以实时感知管理的工程下的文件变更
在 Java 语言中,从 JDK7 开始,新增了java.nio.file.WatchService
类,用来实时监控文件的变化。
1.示例代码
FileWatchedService 类:
package org.learn.file; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.List; /** * 实时监控文件的变化 * * @author zhibo * @date 2019-07-30 20:37 */ public class FileWatchedService { private WatchService watchService; private FileWatchedListener listener; /** * * @param path 要监听的目录,注意该 Path 只能是目录,否则会报错 java.nio.file.NotDirectoryException: /Users/zhibo/logs/a.log * @param listener 自定义的 listener,用来处理监听到的创建、修改、删除事件 * @throws IOException */ public FileWatchedService(Path path, FileWatchedListener listener) throws IOException { watchService = FileSystems.getDefault().newWatchService(); path.register(watchService, /// 监听文件创建事件 StandardWatchEventKinds.ENTRY_CREATE, /// 监听文件删除事件 StandardWatchEventKinds.ENTRY_DELETE, /// 监听文件修改事件 StandardWatchEventKinds.ENTRY_MODIFY); // // path.register(watchService, // new WatchEvent.Kind[]{ // StandardWatchEventKinds.ENTRY_MODIFY, // StandardWatchEventKinds.ENTRY_CREATE, // StandardWatchEventKinds.ENTRY_DELETE // }, // SensitivityWatchEventModifier.HIGH); this.listener = listener; } private void watch() throws InterruptedException { while (true) { WatchKey watchKey = watchService.take(); List<WatchEvent<?>> watchEventList = watchKey.pollEvents(); for (WatchEvent<?> watchEvent : watchEventList) { WatchEvent.Kind kind = watchEvent.kind(); WatchEvent<Path> curEvent = (WatchEvent<Path>) watchEvent; if (kind == StandardWatchEventKinds.OVERFLOW) { listener.onOverflowed(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) { listener.onCreated(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { listener.onModified(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { listener.onDeleted(curEvent); continue; } } /** * WatchKey 有两个状态: * {@link sun.nio.fs.AbstractWatchKey.State.READY ready} 就绪状态:表示可以监听事件 * {@link sun.nio.fs.AbstractWatchKey.State.SIGNALLED signalled} 有信息状态:表示已经监听到事件,不可以接续监听事件 * 每次处理完事件后,必须调用 reset 方法重置 watchKey 的状态为 ready,否则 watchKey 无法继续监听事件 */ if (!watchKey.reset()) { break; } } } public static void main(String[] args) { try { Path path = Paths.get("/Users/zhibo/logs/"); FileWatchedService fileWatchedService = new FileWatchedService(path, new FileWatchedAdapter()); fileWatchedService.watch(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
FileWatchedListener 类:
package org.learn.file; import java.nio.file.Path; import java.nio.file.WatchEvent; public interface FileWatchedListener { void onCreated(WatchEvent<Path> watchEvent); void onDeleted(WatchEvent<Path> watchEvent); void onModified(WatchEvent<Path> watchEvent); void onOverflowed(WatchEvent<Path> watchEvent); }
FileWatchedAdapter 类:
package org.learn.file; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; /** * 文件监听适配器 * * @author zhibo * @date 2019-07-31 11:07 */ public class FileWatchedAdapter implements FileWatchedListener { @Override public void onCreated(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被创建,时间:%s", fileName, now())); } @Override public void onDeleted(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被删除,时间:%s", fileName, now())); } @Override public void onModified(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被修改,时间:%s", fileName, now())); } @Override public void onOverflowed(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被丢弃,时间:%s", fileName, now())); } private String now(){ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); return dateFormat.format(Calendar.getInstance().getTime()); } }
执行以上代码,启动监控任务,然后我在/Users/zhibo/logs/
目录中创建、修改、删除文件,命令如下:
应用程序感知到文件变化,打印日志如下:
2.其实并没有实时
大家可以看到,监控任务基本上是以 10 秒为单位进行日志打印的,也就是说修改一个文件,WatchService 10秒之后才能感知到文件的变化,没有想象中的那么实时。根据以上的经验,推测可能是 WatchService 做了定时的操作,时间间隔为 10 秒。通过翻阅源代码发现,在 PollingWatchService
中确实存在一个固定时间间隔的调度器,如下图:
该调度器的时间间隔有 SensitivityWatchEventModifier
进行控制,该类提供了 3 个级别的时间间隔,分别为2秒、10秒、30秒,默认值为 10秒。SensitivityWatchEventModifier
源码如下:
package com.sun.nio.file; import java.nio.file.WatchEvent.Modifier; public enum SensitivityWatchEventModifier implements Modifier { HIGH(2), MEDIUM(10), LOW(30); private final int sensitivity; public int sensitivityValueInSeconds() { return this.sensitivity; } private SensitivityWatchEventModifier(int var3) { this.sensitivity = var3; } }
通过改变时间间隔来进行验证,将
path.register(watchService, /// 监听文件创建事件 StandardWatchEventKinds.ENTRY_CREATE, /// 监听文件删除事件 StandardWatchEventKinds.ENTRY_DELETE, /// 监听文件修改事件 StandardWatchEventKinds.ENTRY_MODIFY);
修改为:
path.register(watchService, new WatchEvent.Kind[]{ StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE }, SensitivityWatchEventModifier.HIGH);
查看日志,发现正如我们的推断,WatchService 正以每 2 秒的时间间隔感知文件变化。
在 stackoverflow 中也有人提出了该问题,问题:Is Java 7 WatchService Slow for Anyone Else,我的 mac 系统中确实存在该问题,由于手头没有 windows、linux 系统,因此无法进行这两个系统的验证。
加载全部内容