Java实现文件变化监听代码实例
作者:魅Lemon
一、前言
1、简介
在平时的开发过程中,会有很多场景需要实时监听文件的变化,如下:
通过实时监控 mysql 的 binlog 日志实现数据同步
修改配置文件后,希望系统可以实时感知
应用系统将日志写入文件中,日志监控系统可以实时抓取日志,分析日志内容并进行报警
类似 ide 工具,可以实时感知管理的工程下的文件变更
2、三种方法介绍
定时任务 + File#lastModified
WatchService
Apache Commons-IO
二、三种方法实现
1、定时任务 + File#lastModified
通过定时任务,轮训查询文件的最后修改时间,与上一次进行对比。如果发生变化,则说明文件已经修改,进行重新加载或对应的业务逻辑处理
对于文件低频变动的场景,这种方案实现简单,基本上可以满足需求。但该方案如果用在文件目录的变化上,缺点就有些明显了,比如:操作频繁,效率都损耗在遍历、保存状态、对比状态上了,无法充分利用OS的功能。
public class FileWatchDemo { /** * 上次更新时间 */ public static long LAST_TIME = 0L; public static void main(String[] args) throws Exception { // 相对路径代表这个功能相同的目录下 String fileName = "static/test.json"; // 创建文件,仅为实例,实践中由其他程序触发文件的变更 createFile(fileName); // 循环执行 while (true){ long timestamp = readLastModified(fileName); if (timestamp != LAST_TIME) { System.out.println("文件已被更新:" + timestamp); LAST_TIME = timestamp; // 重新加载,文件内容 } else { System.out.println("文件未更新"); } Thread.sleep(1000); } } public static void createFile(String fileName) throws IOException { File file = new File(fileName); if (!file.exists()) { boolean result = file.createNewFile(); System.out.println("创建文件:" + result); } } // 获取文件最后修改时间 public static long readLastModified(String fileName) { File file = new File(fileName); return file.lastModified(); } }
同时该方案存在Bug:在Java8和9的某些版本下,lastModified方法返回时间戳并不是毫秒,而是秒,也就是说返回结果的后三位始终为0
2、WatchService
2.1 介绍
在Java 7中新增了java.nio.file.WatchService,通过它可以实现文件变动的监听。WatchService是基于操作系统的文件系统监控器,可以监控系统所有文件的变化,无需遍历、无需比较,是一种基于信号收发的监控,效率高
相对于方案一,实现起来简单,效率高。不足的地方也很明显,只能监听当前目录下的文件和目录,不能监视子目录。另外对于jdk8之后版本来说,该方案已经实现实时监听,不存在准实时的问题
2.2 简单示例
public class WatchServiceDemo { public static void main(String[] args) throws IOException { // 这里的监听必须是目录 Path path = Paths.get("static"); // 创建WatchService,它是对操作系统的文件监视器的封装,相对之前,不需要遍历文件目录,效率要高很多 WatchService watcher = FileSystems.getDefault().newWatchService(); // 注册指定目录使用的监听器,监视目录下文件的变化; // PS:Path必须是目录,不能是文件; // StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件 path.register(watcher, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.LOW); // 创建一个线程,等待目录下的文件发生变化 try { while (true) { // 获取目录的变化: // take()是一个阻塞方法,会等待监视器发出的信号才返回。 // 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。 // 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个; WatchKey key = watcher.take(); // 处理文件变化事件: // key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。 for (WatchEvent<?> event : key.pollEvents()) { // event.kind():事件类型 if (event.kind() == StandardWatchEventKinds.OVERFLOW) { //事件可能lost or discarded continue; } // 返回触发事件的文件或目录的路径(相对路径) Path fileName = (Path) event.context(); System.out.println("文件更新: " + fileName); } // 每次调用WatchService的take()或poll()方法时需要通过本方法重置 if (!key.reset()) { break; } } } catch (Exception e) { e.printStackTrace(); } } }
2.3 完整示例
创建FileWatchedListener接口
public interface FileWatchedListener { void onCreated(WatchEvent<Path> watchEvent); void onDeleted(WatchEvent<Path> watchEvent); void onModified(WatchEvent<Path> watchEvent); void onOverflowed(WatchEvent<Path> watchEvent); }
创建FileWatchedAdapter 实现类,实现文件监听的方法
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()); } }
创建FileWatchedService 监听类,监听文件
public class FileWatchedService { private WatchService watchService; private FileWatchedListener listener; /** * * @param path 要监听的目录,注意该 Path 只能是目录,否则会报错 java.nio.file.NotDirectoryException: * @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); 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("static"); FileWatchedService fileWatchedService = new FileWatchedService(path, new FileWatchedAdapter()); fileWatchedService.watch(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
3、Apache Commons-IO
3.1 介绍与环境准备
commons-io对实现文件监听的实现位于org.apache.commons.io.monitor包下,基本使用流程如下:
- 自定义文件监听类并继承 FileAlterationListenerAdaptor 实现对文件与目录的创建、修改、删除事件的处理;
- 自定义文件监控类,通过指定目录创建一个观察者 FileAlterationObserver;
- 向监视器添加文件系统观察器,并添加文件监听器;
- 调用并执行。
<!--注意,不同的版本需要不同的JDK支持,2.7需要Java 8及以上版本--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency>
3.2 原理讲解
该方案中监听器本身会启动一个线程定时处理。在每次运行时,都会先调用事件监听处理类的onStart方法,然后检查是否有变动,并调用对应事件的方法;比如,onChange文件内容改变,检查完后,再调用onStop方法,释放当前线程占用的CPU资源,等待下次间隔时间到了被再次唤醒运行。
监听器是基于文件目录为根源的,也可以可以设置过滤器,来实现对应文件变动的监听。过滤器的设置可查看FileAlterationObserver的构造方法:
public FileAlterationObserver(String directoryName, FileFilter fileFilter, IOCase caseSensitivity) { this(new File(directoryName), fileFilter, caseSensitivity); }
3.3 实战演示
创建文件监听器。根据需要在不同的方法内实现对应的业务逻辑处理
public class FileListener extends FileAlterationListenerAdaptor { @Override public void onStart(FileAlterationObserver observer) { super.onStart(observer); // System.out.println("一轮轮询开始,被监视路径:" + observer.getDirectory()); } @Override public void onDirectoryCreate(File directory) { System.out.println("创建文件夹:" + directory.getAbsolutePath()); } @Override public void onDirectoryChange(File directory) { System.out.println("修改文件夹:" + directory.getAbsolutePath()); } @Override public void onDirectoryDelete(File directory) { System.out.println("删除文件夹:" + directory.getAbsolutePath()); } @Override public void onFileCreate(File file) { String compressedPath = file.getAbsolutePath(); System.out.println("新建文件:" + compressedPath); if (file.canRead()) { // TODO 读取或重新加载文件内容 System.out.println("文件变更,进行处理"); } } @Override public void onFileChange(File file) { String compressedPath = file.getAbsolutePath(); System.out.println("修改文件:" + compressedPath); } @Override public void onFileDelete(File file) { System.out.println("删除文件:" + file.getAbsolutePath()); } @Override public void onStop(FileAlterationObserver observer) { super.onStop(observer); // System.out.println("一轮轮询结束,被监视路径:" + fileAlterationObserver.getDirectory()); } }
封装一个文件监控的工具类,核心就是创建一个观察者FileAlterationObserver,将文件路径Path和监听器FileAlterationListener进行封装,然后交给FileAlterationMonitor
public class FileMonitor { private FileAlterationMonitor monitor; public FileMonitor(long interval) { monitor = new FileAlterationMonitor(interval); } /** * 给文件添加监听 * * @param path 文件路径 * @param listener 文件监听器 */ public void monitor(String path, FileAlterationListener listener) { FileAlterationObserver observer = new FileAlterationObserver(new File(path)); monitor.addObserver(observer); observer.addListener(listener); } public void stop() throws Exception { monitor.stop(); } public void start() throws Exception { monitor.start(); } }
调用执行
public class FileRunner { public static void main(String[] args) throws Exception { // 监控间隔 FileMonitor fileMonitor = new FileMonitor(10_000L); fileMonitor.monitor("static", new FileListener()); fileMonitor.start(); } }
到此这篇关于Java实现文件变化监听代码实例的文章就介绍到这了,更多相关Java实现文件变化监听内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!