java学习-io-自定义文件变化监听(custom file change monitor)

简单设计思路:

  • 文件变化实际就是 基于 java.io.File#lastModified (最新更新时间); PS:对于不同的操作系统底层对于文件变更可能会有不同的更新策略, 例如 mac下对于子目录下的变更并不会更新所有父级目录更新时间,对于目录的变更时间只针对于直接子级,对于间接子级的变更是不会有感知的
  • 首先基于 java原生的 EventObject/Listener/Publisher 机制
  • 对于Event(java.util.EventObject) 中的source实际就是java.io.File
  • 对于EventListener(java.util.EventListener) 主要是利用 EventListener(必须)和java.util.Observer (观察者模式,但当前工具类从jdk9开始就被标记为废弃状态)
  • 对于EventPublisher(自定义类)通过继承 java.util.Observable (主要是为了搭配Observer),通过监听event来触发事件执行
  • 对于真正的FileMonitor(自定义类) 通过本地缓存文件相关变更时间数据,并利用定时异步线程主动拉取文件数据,通过历史缓存数据和最新数据进行对比来判断是否变更; 当发生变更时发布事件
  • 通过注册提交listener以及file(event)来创建一个文件变化监控

对于上述执行逻辑为:

  1. 创建Monitor对象,并添加自定义 EventListener,对于添加自定义listener实际就是利用java.util.Observable#addObserver,来添加observer
  2. 提交事件源(要监听的文件/文件夹)
  3. 对于提交事件源首先是为了本地缓存文件(File),并记录初始变更时间
  4. 利用执行中的定时异步线程来主动调用File#lastModified获取最新更新时间, 对于File#lastModified实际会最终调用native 方法来获取物理机器中实际存储的文件数据
  5. 当对于历史数据和最新的数据 当发现文件发生变更后,利用EventPublisher来发布事件(file)
  6. 对于EventPublisher实际是利用java.util.Observable#update 发布事件(PS:需要先调用 java.util.Observable#setChange,否则当调用java.util.Observable#notifyObservers不会触发java.util.Observe#update),利用 java.util.Observe#update来触发EventListener(observer)对于event的处理逻辑

对于以上操作中的监听实际是利用的 主动pull 的操作,而非事件源主动push; 因此对于事件源的变更实际存在一定的延迟性以及资源浪费

 

"talk is cheap , show me the code"

 

/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.io.filechange;

import java.io.File;
import java.util.EventObject;

/**
 * FileChangeEvent
 * 文件变化事件监听
 *
 * @author guoxing
 * @date 2020/12/6 3:25 PM
 * @since
 */
public class FileChangeEvent extends EventObject {

    public FileChangeEvent(File file) {
        super(file);
    }


    @Override
    public File getSource() {
        return (File) super.getSource();
    }
}


/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.io.filechange;

import java.util.EventListener;
import java.util.Observable;
import java.util.Observer;
import java.util.function.Consumer;

/**
 * FileChangeListener
 * 文件变化监听器
 *
 * @author guoxing
 * @date 2020/12/6 3:27 PM
 * @since
 */
@FunctionalInterface
public interface FileChangeListener extends EventListener,
        Consumer<FileChangeEvent>,
        Observer // jdk9后就被标记为过期了
{
    void onFileChange(FileChangeEvent fileChangeEvent);

    @Override
    default void update(Observable o, Object arg) {
        if (arg instanceof FileChangeEvent) {
            onFileChange((FileChangeEvent) arg);
        }
    }

    @Override
    default void accept(FileChangeEvent fileChangeEvent) {
        onFileChange(fileChangeEvent);
    }
}


/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.io.filechange;

import java.io.File;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.stream.Stream;

/**
 * FileChangePublisher
 *
 * @author guoxing
 * @date 2020/12/6 3:33 PM
 * @since
 */
public class FileChangePublisher extends Observable {
    /**
     * 这里利用Observe观察者模式进行相关的监听操作
     */
    /**
     * 添加文件更新监听器
     *
     * @param fileChangeListener
     */
    public void addFileChangeListener(FileChangeListener fileChangeListener) {
        super.addObserver(fileChangeListener);
    }

    /**
     * 发布事件
     *
     * @param fileChangeEvent
     */
    public void publish(FileChangeEvent fileChangeEvent) {
        super.setChanged();
        super.notifyObservers(fileChangeEvent);
    }

    /**
     * 直接通过文件来创建一个{@link FileChangeEvent}
     *
     * @param file
     */
    public void publish(File file) {
        publish(new FileChangeEvent(file));
    }
}

 

/*
 * Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved
 */
package com.xingguo.io.filechange;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * FileMonitorDemo
 * 对于当前文件监听操作实际就是通过判断指定文件的`{@link File#lastModified()}来判断指定文件是否发生变更
 *
 * @author guoxing
 * @date 2020/12/6 3:42 PM
 * @since
 */
public class FileMonitorDemo {
    /**
     * 缓存文件上一次变更时间
     * 对于{@link File#compareTo(File)}中其通过 路径进行比较文件是否相同
     * 对于{@link File#equals(Object)}也是利用了 {@link File#compareTo(File)} 来进行判断
     * 因此可以将{@link File}作为key来保证文件的唯一性
     */
    private Map<File, Long> fileLastModifiedCache = new LinkedHashMap<>();
    // 利用 定时线程实现定时监听
    private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    // 创建文件变化事件发布
    private FileChangePublisher fileChangePublisher = new FileChangePublisher();

    /**
     * 添加文件变化监听器,要求至少有一个监听器
     *
     * @param fileChangeListener
     * @param fileChangeListeners
     */
    public void addFileChangeListener(FileChangeListener fileChangeListener, FileChangeListener... fileChangeListeners) {
        fileChangePublisher.addFileChangeListener(Objects.requireNonNull(fileChangeListener));
        if (Objects.nonNull(fileChangeListeners)) {
            Stream.of(fileChangeListeners)
                    .forEach(fileChangePublisher::addFileChangeListener);
        }
    }

    public void execute(File file) {
        Objects.requireNonNull(file);
        // 通过定时线程去探测文件变化,并不能保证及时性
        executorService.scheduleAtFixedRate(() -> {
            // 在本地系统文件可能会不存在
            Long elderLastModified = fileLastModifiedCache.get(file);
            long lastModified = file.lastModified();
            // 如果文件最终修改时间发生变化,说明文件发生修改,则发布文件变更通知
            if (Objects.isNull(elderLastModified) || elderLastModified < lastModified) {
                System.out.printf("elder:%s;last:%s\n", elderLastModified, lastModified);
                fileChangePublisher.publish(file);
            }
            // 新增/修改最新文件变更时间
            fileLastModifiedCache.put(file, lastModified);
        }, 0, 5, TimeUnit.SECONDS);

    }

    public static void main(String[] args) {
        FileMonitorDemo fileMonitorDemo = new FileMonitorDemo();
        fileMonitorDemo.addFileChangeListener(fileChangeEvent -> {
            System.out.println("处理文件变更事件:" + fileChangeEvent);
        });
        String targetPath = fileMonitorDemo.getClass().getClassLoader().getResource("").getPath();
        fileMonitorDemo.execute(new File(targetPath));
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        String randomName = UUID.randomUUID().toString();
        executorService.scheduleAtFixedRate(() -> {
            File file = new File(targetPath + "/" + randomName + ".text");
            if (file.exists()) {
                file.delete();
                System.out.println("删除文件");
            } else {
                try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                    fileOutputStream.flush();
                    System.out.println("生成文件");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 10, TimeUnit.SECONDS);
    }

}

 

 

 
 
 
posted @ 2020-12-06 20:07  郭星  阅读(325)  评论(0)    收藏  举报