您的位置:首页 > 编程语言 > Java开发

通过 Java 去监测某个目录下的文件变动

2017-03-18 15:29 309 查看
最近处理了一个需求,大概是这样的:

己方搭建好FTP服务器

对方往该服务器的指定目录(假设叫 目录A)上传文件

己方需要将对方上传好的文件(处于上传中状态的文件不能进行处理)解析并更新到数据库中

己方对 目录A 只有 “读”的权限,即,不能对 目录A中的文件进行删除、重命名、移动等操作。

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

开启一个线程,定期去读取 目录A 下的所有文件

将每两次读取的文件列表进行对比,新出现的文件名对应的文件就是对方新上传的

对于新的文件,先记录下它的 大小最后修改时间,然后,隔2秒,再次去读它的这两个属性值。如果这两个值保持不变了,那么就说明文件上发传好了。如果发生了改变,那 么,就再隔2秒再去确定一次。

确定文件上传好了之后,解析文件并上更新到数据库中

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

读取目录A的间隔的不太好设定,设定得小的话,会使得读取的频率太频繁;设定得大的话,又可能导致文件大量积压

获取 大小最后修改时间 这两个属性的值的时间间隔也不好确定,上面说的是 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 最好另建一个 类,这样看起来会比较容易理解。

参考文档

Oracle 官方示例
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息