概要:
使用JNotify和commons-io监控文件变化。
其中:
1.JNotify通过系统API获取文件变更事件,优点是性能好,响应速度快,缺点是需要依赖JNotify库;
2.commons-io通过轮询获取文件变更,优点是不需要额外库,缺点是性能较差,且随着文件数量增多,响应时间也会增加。
说明:
1.以下测试均在Windows 10系统下进行;
2.当报错找不到JNotify库("no jnotify_64bit in java.library.path")时,下载JNotify包(下载地址:https://sourceforge.net/projects/jnotify/files/jnotify/jnotify-0.94/),并将JNotify复制到以下任一目录:
代码:
pom.xml:
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- jnotify -->
<dependency>
<groupId>net.contentobjects.jnotify</groupId>
<artifactId>jnotify</artifactId>
<version>0.94</version>
</dependency>
JNotify:
package com.example.study.monitor;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyListener;
import java.io.IOException;
/**
* 监视系统文件变化
* JNotify通过系统API获取文件变更事件
*/
public class JNotifyFileMonitor implements JNotifyListener {
public static void main(String[] args) throws IOException {
String path1 = "e:/test/001";
String path2 = "e:/test/002";
// 是否监视子目录
boolean watchSubtree = true;
// 需要监视的文件变更类型,此处用4个bit位表示,类似linux的文件访问权限
int mask = JNotify.FILE_CREATED | JNotify.FILE_DELETED | JNotify.FILE_MODIFIED | JNotify.FILE_RENAMED;
int watchId1 = JNotify.addWatch(path1, mask, watchSubtree, new JNotifyFileMonitor());
System.out.println("watchId1 = " + watchId1 + ", 开始监视路径:" + path1);
int watchId2 = JNotify.addWatch(path2, JNotify.FILE_ANY, false, new JNotifyFileMonitor());
System.out.println("watchId2 = " + watchId2 + ", 开始监视路径:" + path2);
while (true) {
}
}
/**
* 文件创建后调用
*
* @param watchId 监视Id
* @param watchPath 被监视的最上层路径
* @param relativePath 创建的文件相对watchPath的相对路径
*/
@Override
public void fileCreated(int watchId, String watchPath, String relativePath) {
System.out.println(String.format("watchId = [%s], 被监视路径 = [%s], 创建文件 = [%s]",
watchId, watchPath, relativePath));
}
/**
* 文件删除后调用
* 删除一个文件夹时:
* 1.若放入回收站,则只有该文件夹的delete通知(从回收站删除时不再有delete通知);
* 2.若不放入回收站直接删除,则该文件夹下所有文件和文件夹(包括子目录)都会有delete通知。
*
* @param watchId 监视Id
* @param watchPath 被监视的最上层路径
* @param relativePath 删除的文件相对watchPath的相对路径
*/
@Override
public void fileDeleted(int watchId, String watchPath, String relativePath) {
System.out.println(String.format("watchId = [%s], 被监视路径 = [%s], 删除文件 = [%s]",
watchId, watchPath, relativePath));
}
/**
* 文件修改后调用
* 当文件夹下的直接文件或文件夹发生【创建、重命名、删除】动作时,该文件夹也会发生变化;【修改】文件时文件夹不发生变化
* 如:被监视的文件夹为/001, 其下有文件夹 ./123/456
* 其中:
* 1.创建文件夹 ./123/456/tmp
* 2.创建文件 ./123/456/1.txt,
* 3.重命名文件夹 ./123/456/tmp -> ./123/456/789
* 4.重命名文件 ./123/456/1.txt -> ./123/456/2.txt
* 5.删除文件夹 ./123/456/789
* 6.删除文件 ./123/456/2.txt
* 除了会触发对应路径的【创建、重命名、删除】事件外,还会触发文件夹 ./123/456 的【修改】事件
* 但:
* 1.修改文件 ./123/456/1.txt
* 2.修改文件夹 ./123/456/789 (比如用touch 789对文件夹进行修改)
* 只会触发对应路径的【修改】事件,不会触发 ./123/456 的【修改】事件
*
* @param watchId 监视Id
* @param watchPath 被监视的最上层路径
* @param relativePath 修改的文件相对watchPath的相对路径
*/
@Override
public void fileModified(int watchId, String watchPath, String relativePath) {
System.out.println(String.format("watchId = [%s], 被监视路径 = [%s], 修改文件 = [%s]",
watchId, watchPath, relativePath));
}
/**
* 文件重命名后调用
*
* @param watchId 监视Id
* @param watchPath 被监视的最上层路径
* @param oldRelativePath 修改前文件名(相对watchPath的相对路径)
* @param newRelativePath 修改后文件名(相对watchPath的相对路径)
*/
@Override
public void fileRenamed(int watchId, String watchPath, String oldRelativePath, String newRelativePath) {
System.out.println(String.format("watchId = [%s], 被监视路径 = [%s], 文件重命名: [%s] -> [%s]",
watchId, watchPath, oldRelativePath, newRelativePath));
}
}
commons-io:
package com.example.study.monitor;
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 CommonsIOFileMonitor implements FileAlterationListener {
public static void main(String[] args) throws Exception {
String path1 = "e:/test/001";
String path2 = "e:/test/002";
FileAlterationListener listener = new CommonsIOFileMonitor();
FileAlterationObserver observer1 = new FileAlterationObserver(path1);
FileAlterationObserver observer2 = new FileAlterationObserver(path2);
observer1.addListener(listener);
observer2.addListener(listener);
// 构造函数的入参为轮询间隔(单位:ms)
FileAlterationMonitor monitor = new FileAlterationMonitor(10000l);
monitor.addObserver(observer1);
monitor.addObserver(observer2);
monitor.start();
}
@Override
public void onStart(FileAlterationObserver fileAlterationObserver) {
System.out.println("一轮轮询开始,被监视路径:" + fileAlterationObserver.getDirectory());
}
@Override
public void onDirectoryCreate(File file) {
System.out.println("创建文件夹:" + file.getPath());
}
@Override
public void onDirectoryChange(File file) {
System.out.println("修改文件夹:" + file.getPath());
}
@Override
public void onDirectoryDelete(File file) {
System.out.println("删除文件夹:" + file.getPath());
}
@Override
public void onFileCreate(File file) {
System.out.println("创建文件:" + file.getPath());
}
@Override
public void onFileChange(File file) {
System.out.println("修改文件:" + file.getPath());
}
@Override
public void onFileDelete(File file) {
System.out.println("删除文件:" + file.getPath());
}
@Override
public void onStop(FileAlterationObserver fileAlterationObserver) {
System.out.println("一轮轮询结束,被监视路径:" + fileAlterationObserver.getDirectory());
}
}
commons-io轮询源码解读:
FileAlterationMonitor(仅展示关键代码):
// 构造函数,默认轮询间隔为10s
public FileAlterationMonitor() {
this(10000L);
}
// 构造函数,设置轮询间隔,初始化观察者数组
public FileAlterationMonitor(long interval) {
this.observers = new CopyOnWriteArrayList();
this.interval = interval;
}
// 启动线程,数据初始化
public synchronized void start() throws Exception {
if (this.running) {
throw new IllegalStateException("Monitor is already running");
} else {
// 遍历观察者
Iterator var1 = this.observers.iterator();
while(var1.hasNext()) {
FileAlterationObserver observer = (FileAlterationObserver)var1.next();
// 观察者初始化,此处遍历所有文件
observer.initialize();
}
this.running = true;
if (this.threadFactory != null) {
this.thread = this.threadFactory.newThread(this);
} else {
this.thread = new Thread(this);
}
// 启动监视线程
this.thread.start();
}
}
// 监视系统文件变化
public void run() {
while(true) {
if (this.running) {
// 遍历观察者
Iterator var1 = this.observers.iterator();
while(var1.hasNext()) {
FileAlterationObserver observer = (FileAlterationObserver)var1.next();
// 检测文件变化,并调用相关方法
observer.checkAndNotify();
}
if (this.running) {
try {
// 线程休眠设定的间隔时间
Thread.sleep(this.interval);
} catch (InterruptedException var3) {
}
continue;
}
}
return;
}
}
FileAlterationObserver(仅展示关键代码):
// 构造函数,文件名大小写敏感默认使用系统规则
public FileAlterationObserver(String directoryName) {
this(new File(directoryName));
}
public FileAlterationObserver(File directory) {
this((File)directory, (FileFilter)null);
}
public FileAlterationObserver(File directory, FileFilter fileFilter) {
this((File)directory, fileFilter, (IOCase)null);
}
public FileAlterationObserver(File directory, FileFilter fileFilter, IOCase caseSensitivity) {
this(new FileEntry(directory), fileFilter, caseSensitivity);
}
protected FileAlterationObserver(FileEntry rootEntry, FileFilter fileFilter, IOCase caseSensitivity) {
this.listeners = new CopyOnWriteArrayList();
if (rootEntry == null) {
throw new IllegalArgumentException("Root entry is missing");
} else if (rootEntry.getFile() == null) {
throw new IllegalArgumentException("Root directory is missing");
} else {
this.rootEntry = rootEntry;
this.fileFilter = fileFilter;
if (caseSensitivity != null && !caseSensitivity.equals(IOCase.SYSTEM)) {
if (caseSensitivity.equals(IOCase.INSENSITIVE)) {
this.comparator = NameFileComparator.NAME_INSENSITIVE_COMPARATOR;
} else {
this.comparator = NameFileComparator.NAME_COMPARATOR;
}
} else {
this.comparator = NameFileComparator.NAME_SYSTEM_COMPARATOR;
}
}
}
// 观察者初始化
public void initialize() throws Exception {
// 获取当前文件(夹)的文件属性
this.rootEntry.refresh(this.rootEntry.getFile());
FileEntry[] children = this.doListFiles(this.rootEntry.getFile(), this.rootEntry);
this.rootEntry.setChildren(children);
}
// 更新文件entity,并返回文件是否发生变化的结果(检测指标为:文件存在情况,文件最后修改时间,文件类型变更(文件夹或文件),文件大小)
public boolean refresh(File file) {
boolean origExists = this.exists;
long origLastModified = this.lastModified;
boolean origDirectory = this.directory;
long origLength = this.length;
this.name = file.getName();
this.exists = Files.exists(file.toPath(), new LinkOption[0]);
this.directory = this.exists && file.isDirectory();
try {
this.lastModified = this.exists ? FileUtils.lastModified(file) : 0L;
} catch (IOException var9) {
this.lastModified = 0L;
}
this.length = this.exists && !this.directory ? file.length() : 0L;
return this.exists != origExists || this.lastModified != origLastModified || this.directory != origDirectory || this.length != origLength;
}
// 获取所有子文件(包括子目录)
private FileEntry[] doListFiles(File file, FileEntry entry) {
File[] files = this.listFiles(file);
FileEntry[] children = files.length > 0 ? new FileEntry[files.length] : FileEntry.EMPTY_FILE_ENTRY_ARRAY;
for(int i = 0; i < files.length; ++i) {
children[i] = this.createFileEntry(entry, files[i]);
}
return children;
}
// 获取子文件
private File[] listFiles(File file) {
File[] children = null;
if (file.isDirectory()) {
children = this.fileFilter == null ? file.listFiles() : file.listFiles(this.fileFilter);
}
if (children == null) {
children = FileUtils.EMPTY_FILE_ARRAY;
}
if (this.comparator != null && children.length > 1) {
Arrays.sort(children, this.comparator);
}
return children;
}
// 创建文件信息(包括获取子目录信息)
private FileEntry createFileEntry(FileEntry parent, File file) {
FileEntry entry = parent.newChildInstance(file);
// 获取子目录下文件信息
entry.refresh(file);
FileEntry[] children = this.doListFiles(file, entry);
entry.setChildren(children);
return entry;
}
// 检测文件变化,并调用相关方法
public void checkAndNotify() {
Iterator var1 = this.listeners.iterator();
while(var1.hasNext()) {
FileAlterationListener listener = (FileAlterationListener)var1.next();
listener.onStart(this);
}
// 检测文件变化
File rootFile = this.rootEntry.getFile();
if (rootFile.exists()) {
this.checkAndNotify(this.rootEntry, this.rootEntry.getChildren(), this.listFiles(rootFile));
} else if (this.rootEntry.isExists()) {
this.checkAndNotify(this.rootEntry, this.rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
}
Iterator var5 = this.listeners.iterator();
while(var5.hasNext()) {
FileAlterationListener listener = (FileAlterationListener)var5.next();
listener.onStop(this);
}
}
// 检测文件变化
private void checkAndNotify(FileEntry parent, FileEntry[] previous, File[] files) {
int c = 0;
FileEntry[] current = files.length > 0 ? new FileEntry[files.length] : FileEntry.EMPTY_FILE_ENTRY_ARRAY;
FileEntry[] var6 = previous;
int var7 = previous.length;
for(int var8 = 0; var8 < var7; ++var8) {
FileEntry entry;
for(entry = var6[var8]; c < files.length && this.comparator.compare(entry.getFile(), files[c]) > 0; ++c) {
// 出现新文件(夹)时,执行文件(夹)创建后方法,doCreate根据文件类型决定调用onDirectoryCreate()或onFileCreate()
current[c] = this.createFileEntry(parent, files[c]);
this.doCreate(current[c]);
}
if (c < files.length && this.comparator.compare(entry.getFile(), files[c]) == 0) {
this.doMatch(entry, files[c]);
this.checkAndNotify(entry, entry.getChildren(), this.listFiles(files[c]));
current[c] = entry;
++c;
} else {
// 删除文件(夹)时,执行文件(夹)删除后方法,doDelete根据文件类型决定调用onDirectoryDelete()或onFileDelete()
this.checkAndNotify(entry, entry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
this.doDelete(entry);
}
}
while(c < files.length) {
current[c] = this.createFileEntry(parent, files[c]);
this.doCreate(current[c]);
++c;
}
parent.setChildren(current);
}
// 判断文件是否发生修改,并执行文件(夹)修改后方法
private void doMatch(FileEntry entry, File file) {
// 通过 refresh() 方法判断文件是否发生变化
if (entry.refresh(file)) {
Iterator var3 = this.listeners.iterator();
while(var3.hasNext()) {
FileAlterationListener listener = (FileAlterationListener)var3.next();
if (entry.isDirectory()) {
listener.onDirectoryChange(file);
} else {
listener.onFileChange(file);
}
}
}
}
参考:
1.https://www.cnblogs.com/songxingzhu/p/8963591.html