本文目录导读:

我将为您提供一个完整的Java WatchService案例,包括文件监控的基本用法和一些实用场景。
基础WatchService案例
import java.io.IOException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 文件监控器 - 基础案例
*/
public class FileWatcher {
private final WatchService watchService;
private final Map<WatchKey, Path> keyPathMap = new HashMap<>();
private volatile boolean running = true;
public FileWatcher() throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
}
/**
* 注册监控目录
*/
public void registerDirectory(Path dir) throws IOException {
// 注册监控事件:创建、修改、删除
WatchKey key = dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
keyPathMap.put(key, dir);
System.out.println("监控目录:" + dir);
}
/**
* 开始监控
*/
public void start() {
System.out.println("文件监控已启动...");
while (running) {
WatchKey key;
try {
// 阻塞等待事件
key = watchService.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("监控被中断");
return;
}
Path dir = keyPathMap.get(key);
// 处理事件
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 检查是否溢出
if (kind == StandardWatchEventKinds.OVERFLOW) {
System.out.println("[警告] 事件溢出,部分事件可能丢失");
continue;
}
// 获取文件名
Path filename = (Path) event.context();
Path fullPath = dir.resolve(filename);
// 处理不同事件类型
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
handleCreate(fullPath);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
handleModify(fullPath);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
handleDelete(fullPath);
}
}
// 重置key以继续接收事件
boolean valid = key.reset();
if (!valid) {
// 目录已被删除
keyPathMap.remove(key);
System.out.println("目录已被删除,停止监控:" + dir);
if (keyPathMap.isEmpty()) {
break;
}
}
}
close();
}
private void handleCreate(Path path) {
System.out.println("[创建] " + path);
if (Files.isDirectory(path)) {
// 递归监控子目录(可选)
// 需要重新注册
}
}
private void handleModify(Path path) {
System.out.println("[修改] " + path);
}
private void handleDelete(Path path) {
System.out.println("[删除] " + path);
}
/**
* 停止监控
*/
public void stop() {
this.running = false;
}
/**
* 关闭资源
*/
public void close() {
try {
watchService.close();
System.out.println("文件监控已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
// 使用示例
public static void main(String[] args) throws IOException {
// 创建监控器
FileWatcher watcher = new FileWatcher();
// 注册要监控的目录
Path monitorDir = Paths.get("/tmp/monitor");
if (!Files.exists(monitorDir)) {
Files.createDirectories(monitorDir);
}
watcher.registerDirectory(monitorDir);
// 可注册多个目录
// watcher.registerDirectory(Paths.get("/home/username/Documents"));
// watcher.registerDirectory(Paths.get("/var/log"));
// 启动监控(在单独线程中运行)
Thread watcherThread = new Thread(watcher::start, "file-watcher-thread");
watcherThread.start();
// 添加关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
watcher.stop();
try {
watcherThread.join(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
System.out.println("按 Ctrl+C 退出程序");
}
}
增强版:递归目录监控
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.TimeUnit;
/**
* 递归文件监控器
*/
public class RecursiveWatcher {
private final WatchService watchService;
public RecursiveWatcher() throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
}
/**
* 递归注册目录及其所有子目录
*/
public void registerAll(final Path start) throws IOException {
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("注册目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* 处理事件(支持动态注册新目录)
*/
public void processEvents() throws IOException {
while (true) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
Path dir = null;
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path filename = pathEvent.context();
// 获取完整的文件路径
if (dir == null) {
// 需要知道是哪个目录触发的
// 这里简化处理
}
Path child = (dir != null) ? dir.resolve(filename) : filename;
System.out.printf("%s: %s\n", kind.name(), child);
// 如果创建了新目录,自动注册
if (kind == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(child)) {
try {
registerAll(child);
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (!key.reset()) {
// 目录不可用
System.out.println("目录无法访问,移除监控");
}
}
}
}
实用案例:配置文件热更新监听器
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 配置文件热更新监听器
*/
public class ConfigWatcher {
public interface ConfigChangeListener {
void onConfigChanged(String configPath, Map<String, String> newConfig);
}
private final WatchService watchService;
private final Map<String, Map<String, String>> configCache = new ConcurrentHashMap<>();
private final Map<String, ConfigChangeListener> listeners = new ConcurrentHashMap<>();
private final ExecutorService executor = Executors.newFixedThreadPool(2);
public ConfigWatcher() throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
}
/**
* 监控配置文件
*/
public void watchConfig(String configPath) throws IOException {
Path path = Paths.get(configPath);
Path parent = path.getParent();
if (parent == null || !Files.exists(path)) {
throw new IllegalArgumentException("配置文件不存在: " + configPath);
}
// 加载初始配置
loadConfig(path);
// 注册监控
parent.register(watchService,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("开始监控配置文件: " + configPath);
}
/**
* 添加配置变更监听器
*/
public void addListener(String configPath, ConfigChangeListener listener) {
listeners.put(configPath, listener);
}
/**
* 加载配置文件
*/
private void loadConfig(Path path) throws IOException {
Map<String, String> config = new ConcurrentHashMap<>();
Files.readAllLines(path, StandardCharsets.UTF_8).stream()
.filter(line -> !line.trim().startsWith("#") && line.contains("="))
.forEach(line -> {
String[] parts = line.split("=", 2);
config.put(parts[0].trim(), parts[1].trim());
});
String key = path.toString();
Map<String, String> oldConfig = configCache.put(key, config);
// 通知监听器
if (oldConfig != null) {
ConfigChangeListener listener = listeners.get(key);
if (listener != null) {
listener.onConfigChanged(key, config);
}
}
}
/**
* 启动监控
*/
public void start() {
executor.submit(() -> {
try {
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
continue;
}
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path filename = pathEvent.context();
Path filePath = (Path) key.watchable();
Path fullPath = filePath.resolve(filename);
// 检查是否是监控的配置文件
String pathStr = fullPath.toString();
if (configCache.containsKey(pathStr)) {
System.out.println("检测到配置文件变更: " + pathStr);
loadConfig(fullPath);
}
}
key.reset();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("监控已停止");
}
});
}
/**
* 停止监控
*/
public void stop() {
executor.shutdownNow();
try {
watchService.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 使用示例
public static void main(String[] args) throws IOException {
// 创建测试配置文件
Path configFile = Paths.get("/tmp/config.properties");
Files.write(configFile,
"name=test\nversion=1.0\n".getBytes(StandardCharsets.UTF_8));
// 创建监控器
ConfigWatcher watcher = new ConfigWatcher();
// 添加监听器
watcher.addListener(configFile.toString(), (path, config) -> {
System.out.println("配置已更新: " + path);
config.forEach((k, v) -> System.out.println(" " + k + " = " + v));
});
// 开始监控
watcher.watchConfig(configFile.toString());
watcher.start();
// 模拟配置修改
Thread.sleep(2000);
Files.write(configFile,
"name=test\nversion=2.0\nenabled=true\n".getBytes(StandardCharsets.UTF_8));
// 等待处理
Thread.sleep(2000);
watcher.stop();
}
}
文件过滤监控器
import java.io.IOException;
import java.nio.file.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
/**
* 带过滤条件的文件监控器
*/
public class FilteredWatcher {
private final WatchService watchService;
private final Set<Predicate<Path>> filters = new HashSet<>();
public FilteredWatcher() throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
}
/**
* 添加过滤器
*/
public void addFilter(Predicate<Path> filter) {
filters.add(filter);
}
/**
* 添加扩展名过滤
*/
public void addExtensionFilter(String... extensions) {
Set<String> extSet = new HashSet<>();
for (String ext : extensions) {
extSet.add(ext.toLowerCase());
}
filters.add(path -> {
String filename = path.getFileName().toString().toLowerCase();
return extSet.stream().anyMatch(filename::endsWith);
});
}
/**
* 注册目录
*/
public void register(Path dir) throws IOException {
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("监控目录: " + dir);
}
/**
* 监控循环
*/
public void watch() throws InterruptedException {
System.out.println("开始监控(带过滤器)...");
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
continue;
}
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path context = pathEvent.context();
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(context);
// 应用过滤器
boolean shouldProcess = filters.isEmpty() ||
filters.stream().allMatch(f -> f.test(fullPath));
if (shouldProcess) {
System.out.printf("%s: %s%n",
event.kind(), fullPath);
// 处理文件内容变化
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
processFile(fullPath);
}
}
}
key.reset();
}
}
private void processFile(Path file) {
// 处理文件内容
System.out.println(" - 处理文件: " + file);
try {
long size = Files.size(file);
System.out.println(" - 文件大小: " + size + " bytes");
} catch (IOException e) {
e.printStackTrace();
}
}
// 使用示例
public static void main(String[] args) throws IOException, InterruptedException {
FilteredWatcher watcher = new FilteredWatcher();
// 只监控txt和log文件
watcher.addExtensionFilter("txt", "log");
// 或者使用自定义过滤器
// watcher.addFilter(path ->
// path.getFileName().toString().startsWith("temp_"));
// 注册目录
Path dir = Paths.get("/tmp/watch_filter");
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
watcher.register(dir);
// 模拟文件操作
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
Files.write(Paths.get("/tmp/watch_filter/test.txt"), "Hello".getBytes());
Thread.sleep(1000);
Files.write(Paths.get("/tmp/watch_filter/ignore.dat"), "Ignore".getBytes());
Thread.sleep(1000);
Files.write(Paths.get("/tmp/watch_filter/app.log"), "Log message".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
watcher.watch();
}
}
使用建议
-
性能考虑:WatchService是事件驱动的,但需要注意:
- 高频率修改事件可能导致性能问题
- 使用OVERFLOW事件处理事件丢失情况
- 考虑事件积压和线程池配置
-
可靠性:
- 确保正确调用
key.reset()以继续接收事件 - 处理无效的WatchKey(目录被删除等)
- 考虑使用异常处理和重试机制
- 确保正确调用
-
适用场景:
- 配置文件热更新
- 日志文件监控
- 上传文件自动处理
- 目录同步工具
这些案例覆盖了Java WatchService的主要使用场景,可以根据实际需求选择合适的实现。