WatchService 文件监听
你做过热重载功能吗?当源代码改变时,程序自动重新加载。
这背后的原理就是文件系统监听——操作系统会告诉我们「某个目录下的文件变了」。
JDK 7 的 WatchService 就是干这个的。
基本用法
三步走:
java
// 1. 创建 WatchService
WatchService ws = FileSystems.getDefault().newWatchService();
// 2. 注册要监听的目录
Path dir = Path.of("watched_dir");
dir.register(ws,
StandardWatchEventKinds.ENTRY_CREATE, // 文件创建
StandardWatchEventKinds.ENTRY_MODIFY, // 文件修改
StandardWatchEventKinds.ENTRY_DELETE); // 文件删除
// 3. 阻塞等待事件
while (true) {
WatchKey key = ws.take(); // 阻塞直到有事件
for (WatchEvent<?> event : key.pollEvents()) {
Path changed = (Path) event.context();
System.out.println(event.kind() + ": " + changed);
}
key.reset(); // 必须重置,继续监听
}监听类型
| 事件类型 | 说明 |
|---|---|
ENTRY_CREATE | 文件被创建(或移动进) |
ENTRY_MODIFY | 文件被修改 |
ENTRY_DELETE | 文件被删除(或移动出) |
OVERFLOW | 事件溢出(系统事件太多,可能丢失) |
注意:不同操作系统对 ENTRY_MODIFY 的敏感度不同。Windows 通常一次修改只触发一次,但 Linux 可能会触发多次。
完整示例:热重载配置
java
public class ConfigWatcher {
private final WatchService watcher;
private final Map<WatchKey, Path> keys = new HashMap<>();
public ConfigWatcher(Path dir) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
registerAll(dir);
}
private void registerAll(Path dir) throws IOException {
// 递归注册所有子目录
Files.walk(dir)
.filter(Files::isDirectory)
.forEach(p -> {
try {
WatchKey key = p.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
keys.put(key, p);
} catch (IOException e) {
e.printStackTrace();
}
});
}
public void processEvents() {
while (true) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
return;
}
Path dir = keys.get(key);
if (dir == null) continue;
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
Path child = dir.resolve(filename);
System.out.println(kind + ": " + child);
// 如果是子目录被创建,注册它
if (kind == StandardWatchEventKinds.ENTRY_CREATE &&
Files.isDirectory(child)) {
registerAll(child);
}
// 如果是配置文件被修改,热重载
if (kind == StandardWatchEventKinds.ENTRY_MODIFY &&
filename.toString().endsWith(".properties")) {
reloadConfig(child);
}
}
key.reset();
}
}
private void reloadConfig(Path configFile) {
System.out.println("热重载: " + configFile);
// 重新加载配置...
}
}使用场景
- 热重载:配置文件、代码修改后自动重载
- 文件同步:监听源目录,同步到目标目录
- 自动化构建:检测文件变化,自动触发构建
- 内容索引:检测文件变化,更新搜索索引
注意事项
OVERFLOW 处理:事件太多时会触发这个事件,可能丢失部分事件。需要根据场景决定如何处理(忽略、重启监听等)。
必须调用
reset():不重置的话,这个 key 就不会再响应后续事件了。只支持目录:WatchService 监听的是目录变化,不是单个文件。被监听目录下的文件变化会触发事件。
跨平台差异:macOS 有时需要额外配置。
WatchService 让你的程序拥有了「耳朵」,听得到文件系统的变化。
下一节,我们来总结 NIO.2 的全部内容。
