通过 Java 去监测某个目录下的文件变动 (File Watch Service方式)

最近处理了一个需求,大概是这样的:

  1. 己方搭建好FTP服务器
  2. 对方往该服务器的指定目录(假设叫 目录A)上传文件
  3. 己方需要将对方上传好的文件(处于上传中状态的文件不能进行处理)解析并更新到数据库中
  4. 己方对 目录A 只有 “读”的权限,即,不能对 目录A中的文件进行删除、重命名、移动等操作。


对于这个需求,我一开始想出的 解决方案 是:

  1. 开启一个线程,定期去读取 目录A 下的所有文件(或:轮询)
  2. 将每两次读取的文件列表进行对比,新出现的文件名对应的文件就是对方新上传的
  3. 对于新的文件,先记录下它的 大小 和 最后修改时间,然后,隔2秒,再次去读它的这两个属性值。如果这两个值保持不变了,那么就说明文件上发传好了。如果发生了改变,那 么,就再隔2秒再去确定一次。
  4. 确定文件上传好了之后,解析文件并上更新到数据库中

 

这个方案在一般情况下是可以胜任的,但是它隐藏以下两个小问题:

  1. 读取目录A的间隔的不太好设定,设定得小的话,会使得读取的频率太频繁;设定得大的话,又可能导致文件大量积压
  2. 获取 大小 和 最后修改时间 这两个属性的值的时间间隔也不好确定,上面说的是 2秒,是我自己的假想。因为当遇到大文件时,极有可能在2秒之内是不会传完的。如果FTP是搭建在 windows 操作系统上的话,会有下面这个问题: 一个文件在传输之初时,就已经将文件大小确定了,在传输过程中,通过 java 中 File 类的 lengh()去查看的话,它的值是不会发生变化的。 对于 最后修改时间这个属性,只有在文件创建之初和文件传输完比之后,才会发变改变,在传输过程中,通过 java 的 File 类的 lastModifiedTime() 去查看的话,它的值也是不会发变化的;如果FTP是搭建在 Unix 操作系统上的话,是没有上面这个问题,在整个文件传输过程中, 大小 和 最后修改时间 这两个属性是一直在变化的。(我在 CentOS7 上验证过)


既然上面这个方案有缺陷,那就想想其他方案吧。 
后来,在同事的点拨下,找到了 JDK7 中增加的新的 API:File Watch Service。

这个API的思路,其实跟 观察者 模式是一样的:对指定的目录注册一个 Watcher,当目录下的文件发生变化时,Java通知你这个 Watcher 说文件变化了。这样一来,你就可以进行处理了。

下面直接上代码:

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import static java.nio.file.StandardWatchEventKinds.*;

public class Sample {

    private WatchService watcher;

    private Path path;

    public Sample(Path path) throws IOException {
        this.path = path;
        watcher = FileSystems.getDefault().newWatchService();
        this.path.register(watcher, OVERFLOW, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
    }

    public void handleEvents() throws InterruptedException {
        // start to process the data files
        while (true) {
            // start to handle the file change event
            final WatchKey key = watcher.take();

            for (WatchEvent<?> event : key.pollEvents()) {
                // get event type
                final WatchEvent.Kind<?> kind = event.kind();

                // get file name
                @SuppressWarnings("unchecked")
                final WatchEvent<Path> pathWatchEvent = (WatchEvent<Path>) event;
                final Path fileName = pathWatchEvent.context();

                if (kind == ENTRY_CREATE) {

                    // 说明点1
                    // create a new thread to monitor the new file
                    new Thread(new Runnable() {
                        public void run() {
                            File file = new File(path.toFile().getAbsolutePath() + "/" + fileName);
                            boolean exist;
                            long size = 0;
                            long lastModified = 0;
                            int sameCount = 0;
                            while (exist = file.exists()) {
                                // if the 'size' and 'lastModified' attribute keep same for 3 times,
                                // then we think the file was transferred successfully
                                if (size == file.length() && lastModified == file.lastModified()) {
                                    if (++sameCount >= 3) {
                                        break;
                                    }
                                } else {
                                    size = file.length();
                                    lastModified = file.lastModified();
                                }
                                try {
                                    Thread.sleep(500);
                                } catch (InterruptedException e) {
                                    return;
                                }
                            }
                            // if the new file was cancelled or deleted
                            if (!exist) {
                                return;
                            } else {
                                // update database ...
                            } 
                        }
                    }).start();
                } else if (kind == ENTRY_DELETE) {
                    // todo
                } else if (kind == ENTRY_MODIFY) {
                    // todo
                } else if (kind == OVERFLOW) {
                    // todo
                }
            }

            // IMPORTANT: the key must be reset after processed
            if (!key.reset()) {
                return;
            }
        }
    }

    public static void main(String args[]) throws IOException, InterruptedException {
        new Sample(Paths.get(args[0])).handleEvents();
    }
}

对于上面代码中 “说明点1” ,补充以下几点说明:

这种通过判断 文件大小 和 文件最后修改时间 处理方式只限于 Unix 操作系统,原因在上面已经说过了。
对于 windows 系统,应该在产生 ENTRY_CREATE 这个事件后,继续监听,直到产了一个该文件的“ENTRY_MODIFY”事件,或者 ENTRY_DELETE 事件,才说明该文件是传输完毕或者被取消传输了。
内嵌的 Thread 最好另建一个 类,这样看起来会比较容易理解。


 

另外:还可以应用commons-io包的方法,启一个Listener监听器,去监测某个目录下的文件变动

https://blog.csdn.net/weixin_41888813/article/details/84861103


参考文档
Oracle 官方示例

 


来源于:

https://blog.csdn.net/rainbow702/article/details/63254945

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值