【java技术】文件变更监控方式

前言

因为所从事行业的原因,经常涉及到文件跨网闸传输的需求,我们知道数据跨网闸传输有两种方式:数据库和文件方式,由于我们的数据涉及到图片流,所以我们的跨网闸方式是文件摆渡,又会涉及到文件的上传、读取,尤其文件读取以往实现方式是定时线程监控文件是否有新增和变更,但是这样实现业务逻辑比较复杂,经常会出现bug,定位也不方便,所以想对这一块业务逻辑做一个优化;

思路

通过监听模式对文件的增删改查进行监控,这样业务逻辑比较简单,这里对常用的文件监听方式进行简单总结,涉及jdk自带的webservice和common-io工具的FileAlterationListenerAdaptor

实现方式

1)webservice

Java 7中新增了java.nio.file.WatchService,通过它可以实现文件变动的监听。WatchService是基于操作系统的文件系统监控器,可以监控系统所有文件的变化,无需遍历、无需比较,是一种基于信号收发的监控,效率高。

package com.sk.test;

import java.io.IOException;
import java.nio.file.*;

public class WatchServiceDemo {

    public static void main(String[] args) throws IOException {

        // 这里的监听必须是目录
        Path path = Paths.get("G:\\var\\test");
        // 创建WatchService,它是对操作系统的文件监视器的封装,相对之前,不需要遍历文件目录,效率要高很多
        WatchService watcher = FileSystems.getDefault().newWatchService();
        // 注册指定目录使用的监听器,监视目录下文件的变化;
        // PS:Path必须是目录,不能是文件;
        // StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件
        path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);

        // 创建一个线程,等待目录下的文件发生变化
        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();
        }
    }

}

通过WatchService监听文件的类型也变得更加丰富:
ENTRY_CREATE 目标被创建
ENTRY_DELETE 目标被删除
ENTRY_MODIFY 目标被修改
OVERFLOW 一个特殊的Event,表示Event被放弃或者丢失

执行结果:

Connected to the target VM, address: '127.0.0.1:1635', transport: 'socket'
文件更新: aaa
文件更新: aaa
文件更新: test.txt

缺点
只能监听当前目录下的文件和目录,不能监视子目录,而且我们也看到监听只能算是准实时的,而且监听时间只能取API默认提供的三个值。

2)FileAlterationListenerAdaptor

commons-io对实现文件监听的实现位于org.apache.commons.io.monitor包下,基本使用流程如下:

a、自定义文件监听类并继承 FileAlterationListenerAdaptor 实现对文件与目录的创建、修改、删除事件的处理;
b、自定义文件监控类,通过指定目录创建一个观察者 FileAlterationObserver
c、向监视器添加文件系统观察器,并添加文件监听器;
d、调用并执行。

引入依赖:

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

第一步:创建文件监听器。根据需要在不同的方法内实现对应的业务逻辑处理。

package com.sk.service;

import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationObserver;

import java.io.File;

public class FileListener extends FileAlterationListenerAdaptor {

    @Override
    public void onStart(FileAlterationObserver observer) {
        super.onStart(observer);
        System.out.println("onStart");
    }

    @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("onStop");
    }
}

第二步:封装一个文件监控的工具类,核心就是创建一个观察者FileAlterationObserver,将文件路径Path和监听器FileAlterationListener进行封装,然后交给FileAlterationMonitor

package com.sk.service;

import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;

import java.io.File;

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();

    }
}

第三步:调用并执行:

package com.sk.service;

public class FileRunner {

    public static void main(String[] args) throws Exception {
        FileMonitor fileMonitor = new FileMonitor(1000);
        fileMonitor.monitor("G:\\var\\test", new FileListener());
        fileMonitor.start();
    }

}

执行程序,会发现每隔1秒输入一次日志。当文件发生变更时,也会打印出对应的日志:

Connected to the target VM, address: '127.0.0.1:1954', transport: 'socket'
onStart
onStop
onStart
修改:G:\var\test\test.txt
onStop
onStart
onStop
Disconnected from the target VM, address: '127.0.0.1:1954', transport: 'socket'
onStart
onStop

当然,对应的监听时间间隔,可以通过在创建FileMonitor时进行修改。

该方案中监听器本身会启动一个线程定时处理。在每次运行时,都会先调用事件监听处理类的onStart方法,然后检查是否有变动,并调用对应事件的方法;比如,onChange文件内容改变,检查完后,再调用onStop方法,释放当前线程占用的CPU资源,等待下次间隔时间到了被再次唤醒运行。

监听器是基于文件目录为根源的,也可以可以设置过滤器,来实现对应文件变动的监听。过滤器的设置可查看FileAlterationObserver的构造方法:

public FileAlterationObserver(String directoryName, FileFilter fileFilter, IOCase caseSensitivity) {
    this(new File(directoryName), fileFilter, caseSensitivity);
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值