一、场景
我们可能需要监听目录或者文件的变化,做出相应操作。
比如 配置文件变化后,需要重新加载配置。
二、Java监听目录变化的实现方式
参考:
https://zq99299.github.io/java-tutorial/essential/io/notification.html#watch-%E6%9C%8D%E5%8A%A1%E6%A6%82%E8%BF%B0
三、注意事项
1、监听的事件发生时,java应用程序不会实时感知到,而是有一定的间隔。监听程序默认10s检测一次。
代码:
PollingWatchService.java
// enables periodic polling
void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {
synchronized (this) {
// update the events
this.events = events;
// create the periodic task
Runnable thunk = new Runnable() { public void run() { poll(); }};
this.poller = scheduledExecutor
.scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);
}
}
SensitivityWatchEventModifier.java
public enum SensitivityWatchEventModifier implements Modifier {
/**
* High sensitivity.
*/
HIGH(2),
/**
* Medium sensitivity.
*/
MEDIUM(10),
/**
* Low sensitivity.
*/
LOW(30);
/**
* Returns the sensitivity in seconds.
*/
public int sensitivityValueInSeconds() {
return sensitivity;
}
private final int sensitivity;
private SensitivityWatchEventModifier(int sensitivity) {
this.sensitivity = sensitivity;
}
}
2、监听的修改事件发生时,文件不一定被修改完成。实际上内容flush到文件时,就会触发修改事件。因此有可能读到不完整的文件内容。
证据1:WatchService的注释中写道:
When an event is reported to indicate that a file in a watched
directory has been modified then there is no guarantee that the
program (or programs) that have modified the file have completed. Care
should be taken to coordinate access with other programs that may be
updating the file. The FileChannel class defines methods to lock
regions of a file against access by other programs.
证据2:
下述代码验证
四、代码示例和验证
import com.sun.nio.file.SensitivityWatchEventModifier;
import java.io.*;
import java.nio.file.*;
import java.util.Date;
public class WatchFileDemo {
public static void main(String[] args) {
String dir = "/tmp/";
new Thread(() -> {
watchFile(dir);
}).start();
String file1 = dir + "xx.json";
testWriteFile1(file1);
String file2 = dir + "yy.json";
testWriteFile2(file2);
}
public static void watchFile(String dir) {
try (WatchService watchService = FileSystems.getDefault().newWatchService()){
Path path = Paths.get(dir);
//注册文件 创建、修改 事件的监听
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
while (true) {
//返回排队的 key。如果没有排队的密钥可用,则此方法等待。
WatchKey key = watchService.take();
for (WatchEvent<?> watchEvent : key.pollEvents()) {
WatchEvent.Kind<?> kind = watchEvent.kind();
WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
//检索与事件关联的文件名。文件名被存储为事件的上下文
Path filename = watchEventPath.context();
System.out.println(new Date() + " 文件:" + filename + ",发生事件:" + kind.name());
//无论注册了什么事件,都可能收到一个 OVERFLOW 事件(表名事件被丢失或者丢弃),可以处理或则忽略
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
} else if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println(filename + "被创建");
// System.out.println(readFile(dir + filename.toString()));
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println(filename + "被修改");
// System.out.println(readFile(dir + filename.toString()));
}
}
//在处理完事件后,需要通过 reset() 将事件重置 ready 状态。
//如果此方法返回false,则该 key 不再有效,循环可以退出。
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 只写。等到最后再保存
* @param filename
*/
public static void testWriteFile1(String filename) {
System.out.println(new Date() + ":开始写,test1");
try {
FileWriter writer = new FileWriter(filename);
// BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
for (int i=0; i< 10; i++) {
Thread.sleep(5000);
writer.write("test1\n");
System.out.println(new Date() + ":写test1");
}
writer.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":写完毕,test1");
}
/**
* 写的时候每次都flush
* @param filename
*/
public static void testWriteFile2(String filename) {
System.out.println(new Date() + ":开始写,test2");
try {
FileWriter writer = new FileWriter(filename);
// BufferedWriter writer = new BufferedWriter();
for (int i=0; i< 10; i++) {
Thread.sleep(5000);
writer.write("test2\n");
writer.flush();
System.out.println(new Date() + ":写test2");
}
writer.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + ":写完毕,test2");
}
public static String readFile(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename));){
String line;
StringBuilder result = new StringBuilder();
while ((line = reader.readLine()) != null) {
result.append(line).append(System.lineSeparator());
}
return result.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
执行结果:
Thu Sep 09 17:29:59 CST 2021:开始写,test1
Thu Sep 09 17:30:01 CST 2021 文件:xx.json,发生事件:ENTRY_MODIFY
xx.json被修改
Thu Sep 09 17:30:04 CST 2021:写test1
Thu Sep 09 17:30:09 CST 2021:写test1
Thu Sep 09 17:30:14 CST 2021:写test1
Thu Sep 09 17:30:19 CST 2021:写test1
Thu Sep 09 17:30:24 CST 2021:写test1
Thu Sep 09 17:30:29 CST 2021:写test1
Thu Sep 09 17:30:34 CST 2021:写test1
Thu Sep 09 17:30:39 CST 2021:写test1
Thu Sep 09 17:30:44 CST 2021:写test1
Thu Sep 09 17:30:49 CST 2021:写test1
Thu Sep 09 17:30:49 CST 2021:写完毕,test1
Thu Sep 09 17:30:49 CST 2021:开始写,test2
Thu Sep 09 17:30:51 CST 2021 文件:xx.json,发生事件:ENTRY_MODIFY
xx.json被修改
Thu Sep 09 17:30:51 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:30:54 CST 2021:写test2
Thu Sep 09 17:30:55 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:30:59 CST 2021:写test2
Thu Sep 09 17:31:01 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:04 CST 2021:写test2
Thu Sep 09 17:31:05 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:09 CST 2021:写test2
Thu Sep 09 17:31:11 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:14 CST 2021:写test2
Thu Sep 09 17:31:15 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:19 CST 2021:写test2
Thu Sep 09 17:31:21 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:24 CST 2021:写test2
Thu Sep 09 17:31:25 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:29 CST 2021:写test2
Thu Sep 09 17:31:31 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:34 CST 2021:写test2
Thu Sep 09 17:31:35 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
Thu Sep 09 17:31:39 CST 2021:写test2
Thu Sep 09 17:31:39 CST 2021:写完毕,test2
Thu Sep 09 17:31:41 CST 2021 文件:yy.json,发生事件:ENTRY_MODIFY
yy.json被修改
可以看到测试1每次write不一定被检测到修改事件,测试2每次write后都调用flush函数,就可以每次都检测到修改事件。