zoukankan      html  css  js  c++  java
  • 监听文件修改的四种方法

    遇到了监听配置文件是否被修改的需求,因功能规模小,没有加入 Apollo、config 等组件,所以得自己实现


    1. 自行实现

    第一想法是用定时任务去实现,下面是笔者的实现思路:FileModifyManager 来监听管理全部文件,要实现监听接口 FileListener 并传入给 FileModifyManager ,每当文件发生变化就调用监听接口的方法 doListener


    1.1 FileListener

    @FunctionalInterface
    public interface FileListener {
        void doListener();
    }
    

    看了 Hutool 文档才知道这种设计叫钩子函数,那笔者和 Hutool 作者思路也有相似之处



    1.2 FileModifyManager

    /**
     * @author Howl
     * @date 2022/01/15
     */
    public class FileModifyManager {
    
        // 存放监听的文件及 FileNodeRunnable 节点
        private static ConcurrentHashMap<File, FileNodeRunnable> data = new ConcurrentHashMap<>(16);
    
        // 线程池执行定时监听任务
        private static ScheduledExecutorService service = Executors.newScheduledThreadPool(20);
    
        // 单例模式--双重校验锁
        private volatile static FileModifyManager instance = null;
    
        private FileModifyManager() {
        }
    
        public static FileModifyManager getInstance() {
            if (instance == null) {
                synchronized (FileModifyManager.class) {
                    if (instance == null) {
                        instance = new FileModifyManager();
                    }
                }
            }
            return instance;
        }
    
        // 开始监听,默认 10 秒监听一次
        public FileModifyManager startWatch(File file, FileListener fileListener) throws Exception {
            return startWatch(file, fileListener, 0, 1000 * 10, TimeUnit.MILLISECONDS);
        }
    
        public FileModifyManager startWatch(File file, FileListener fileListener, long delay, long period, TimeUnit timeUnit) throws Exception {
            FileNodeRunnable fileNodeRunnable = addFile(file, fileListener);
            ScheduledFuture<?> scheduledFuture = service.scheduleAtFixedRate(fileNodeRunnable, delay, period, timeUnit);
            fileNodeRunnable.setFuture(scheduledFuture);
            return instance;
        }
    
        // 停止监听
        public FileModifyManager stopWatch(File file) {
            return stopWatch(file, true);
        }
    
        public FileModifyManager stopWatch(File file, boolean mayInterruptIfRunning) {
            FileNodeRunnable fileNodeRunnable = data.get(file);
            fileNodeRunnable.getFuture().cancel(mayInterruptIfRunning);
            removeFile(file);
            return instance;
        }
    
        // 是否监听
        public boolean isWatching(File file) {
            return containsFile(file);
        }
    
        // 监听列表
        public Set listWatching() {
            return getFileList();
        }
    
    
        // 管理文件
        private FileNodeRunnable addFile(File file, FileListener fileListener) throws Exception {
            isFileExists(file);
            FileNodeRunnable fileNodeRunnable = new FileNodeRunnable(file, fileListener, file.lastModified());
            data.put(file, fileNodeRunnable);
            return fileNodeRunnable;
        }
    
        private void removeFile(File file) {
            data.remove(file);
        }
    
        private boolean containsFile(File file) {
            return data.containsKey(file);
        }
    
        private Set getFileList() {
            return data.keySet();
        }
    
        // 判断文件存在与否
        private void isFileExists(File file) throws Exception {
            if (!file.exists()) {
                throw new Exception("文件或路径不存在");
            }
        }
    
        // 文件节点及其定时任务
        private class FileNodeRunnable implements Runnable {
    
            private File file;
            private long lastModifyTime;
            private FileListener listener;
            private ScheduledFuture future;
    
            FileNodeRunnable(File file, FileListener listener, long lastModifyTime) {
                this.file = file;
                this.listener = listener;
                this.lastModifyTime = lastModifyTime;
            }
    
            @Override
            public void run() {
                if (this.lastModifyTime != file.lastModified()) {
                    System.out.println(file.toString() + " lastModifyTime is " + this.lastModifyTime);
                    this.lastModifyTime = file.lastModified();
                    listener.doListener();
                }
            }
    
            public ScheduledFuture getFuture() {
                return future;
            }
    
            public void setFuture(ScheduledFuture future) {
                this.future = future;
            }
        }
    }
    
    

    对外只暴露 startWatch、stopWatch、listWatching 三个方法,入参为 File 和 FileListener

    Hutool 也是用了 HashMap 来存放对应的关系表,那笔者思路还是挺清晰的



    1.3 使用案例

    public class FileTest {
        public static void main(String[] args) throws Exception {
    
            File file1 = new File("C:\\Users\\Howl\\Desktop\\123.txt");
            File file2 = new File("C:\\Users\\Howl\\Desktop\\1234.txt");
    
            FileModifyManager manager = FileModifyManager.getInstance();
    
            manager.startWatch(file1,() -> System.out.println("123.txt 文件改变了"))
                    .startWatch(file2,() -> System.out.println("1234.txr 文件改变了"));
            
        }
    }
    






    2. WatchService

    WatchService 是利用本机操作系统的文件系统来实现监控文件目录(监控目录),于 JDK1.7 引入的位于 NIO 包下的新机制,所以使用方式和 NIO 也很相似


    JDK 自带的 watchService 的缺点是修改文件会触发两次事件,因操作系统有不同情况:

    • 修改了文件的 meta 信息和日期
    • 写时复制效果,即旧文件改名,并将内容复制到新建的文件里

    watchService 只能监控本目录的内容,不能检测子目录里的内容,如需监控则遍历添加子目录


    public class WatchServiceTest {
        public static void main(String[] args) throws IOException, InterruptedException {
    
            // 目录路径,不能输入文件否则报错
            Path path = Paths.get("C:\\Users\\Howl\\Desktop");
    
            // 获取监听服务
            WatchService watchService = FileSystems.getDefault().newWatchService();
    
            // 只注册修改事件(还有创建和删除)
            path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
    
            // 监听
            while (true) {
    
                // 获取监听到的事件 key
                WatchKey watchKey = watchService.poll(3 * 1000, TimeUnit.MILLISECONDS);
    
                // poll 的返回有可能为 null
                if (watchKey == null) {
                    continue;
                }
    
                // 遍历这些事件
                for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
                    if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                        Path watchPath = (Path) watchEvent.context();
                        File watchFile = watchPath.toFile();
                        System.out.println(watchFile.toString() + "文件修改了");
                    }
                }
    
                // watchKey 复原,用于下次监听
                watchKey.reset();
            }
        }
    }
    






    3. Hutool(推荐)

    Hutool 是国人维护的工具集,使用别人的轮子,总比自己重复造轮子高效(但也要了解轮子的设计思路),hutool 底层还是使用 WatchService ,其解决了修改文件会触发两次事件,思路是在某个毫秒数范围内的修改视为同一个修改。还可以监控子目录,思路是递归遍历


    3.1 添加依赖

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.19</version>
    </dependency>
    

    参考文档 Hutool



    3.2 示例

    public class HutoolTest {
        public static void main(String[] args) throws Exception {
    
            File file = new File("C:\\Users\\Howl\\Desktop\\123.txt");
    
            // 监听文件修改
            WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY);
    
            // 设置钩子函数
            watchMonitor.setWatcher(new SimpleWatcher() {
                @Override
                public void onModify(WatchEvent<?> event, Path currentPath) {
                    System.out.println(((Path) event.context()).toFile().toString() + "修改了");
                }
            });
    
            // 设置监听目录的最大深入,目录层级大于制定层级的变更将不被监听,默认只监听当前层级目录
            watchMonitor.setMaxDepth(1);
    
            // 启动监听
            watchMonitor.start();
        }
    }
    

    思路是继承 Thread 类,然后 run 方法一直循环监听 watchService 事件







    4. commons-io

    commons-io 是 Apache 提供的实现 I/O 操作的工具集


    4.1 添加依赖

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
    


    4.2 示例

    稍微看了一下使用的是观察者模式

    public class CommonsTest {
        public static void main(String[] args) throws Exception {
    
            // 也是只能写目录
            String filePath = "C:\\Users\\Howl\\Desktop";
    
            // 文件观察者
            FileAlterationObserver observer = new FileAlterationObserver(filePath);
    
            // 添加监听
            observer.addListener(new FileAlterationListenerAdaptor() {
                @Override
                public void onFileChange(File file) {
                    System.out.println(file.toString() + "文件修改了");
                }
            });
    
            // 监视器
            FileAlterationMonitor monitor = new FileAlterationMonitor(10);
    
            // 添加观察者
            monitor.addObserver(observer);
    
            // 启动线程
            monitor.start();
        }
    }
    


  • 相关阅读:
    SpringBoot入门篇--读取资源文件配置
    SpringBoot入门篇--使用Thymeleaf模板引擎进行页面的渲染
    SpringBoot入门篇--热部署
    NOI2017 游记
    BZOJ 2754 【SCOI2012】 喵星球上的点名
    codeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
    BZOJ 4407 于神之怒加强版
    BZOJ 2956 模积和
    BZOJ 4584 【APIO2016】 赛艇
    BZOJ 4591 【SHOI2015】 超能粒子炮·改
  • 原文地址:https://www.cnblogs.com/Howlet/p/15808845.html
Copyright © 2011-2022 走看看