WatchService
是 Java NIO 包 (java.nio.file) 中提供的一个用于监控文件系统变化的 API。它允许应用程序监听目录中的文件创建、修改和删除事件。
基本原理
WatchService
使用操作系统提供的文件系统通知机制:
-
Windows: 使用 ReadDirectoryChangesW
-
Linux: 使用 inotify
-
Mac OS X: 使用 FSEvents
WatchService 和定时任务轮询是两种不同的文件监控策略,它们在资源消耗方面有显著差异。
资源消耗对比
特性 | WatchService | 定时任务轮询 |
---|---|---|
工作机制 | 基于操作系统事件通知 | 定期主动扫描文件系统 |
CPU使用 | 低(事件驱动,空闲时几乎不消耗CPU) | 高(每次扫描都需要CPU计算) |
内存使用 | 中等(维护事件队列和状态) | 取决于扫描范围和频率 |
I/O操作 | 极少(仅在有变化时触发) | 高(每次扫描都需要读取文件系统) |
响应延迟 | 近实时(毫秒级) | 取决于轮询间隔(秒级或更长) |
可扩展性 | 好(可监控大量文件) | 差(大量文件时性能下降明显) |
直接上实现代码:
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
/*
监控指定目录下文件的创建、删除和修改
*/
public class DirectoryWatcher {
public static void main(String[] args) throws Exception {
// 1. 创建WatchService实例
WatchService watcher = FileSystems.getDefault().newWatchService();
// 2. 注册要监控的目录
Path dir = Paths.get("D:/2"); // 替换为你要监控的目录
WatchKey key = dir.register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
System.out.println("开始监控: " + dir);
// 3. 事件处理循环
while (true) {
// 等待并获取下一个WatchKey
key = watcher.take();
// 处理所有事件
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 处理OVERFLOW事件(可能丢失或丢弃的事件)
if (kind == OVERFLOW) {
continue;
}
// 获取事件上下文(通常是文件名)
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
System.out.printf("事件类型: %s, 文件: %s%n", kind.name(), filename);
// 这里可以添加自定义的业务处理逻辑
if (kind == ENTRY_CREATE) {
System.out.println("新文件创建: " + filename);
} else if (kind == ENTRY_DELETE) {
System.out.println("文件删除: " + filename);
} else if (kind == ENTRY_MODIFY) {
System.out.println("文件修改: " + filename);
}
}
// 4. 重置WatchKey以继续接收事件
boolean valid = key.reset();
if (!valid) {
System.out.println("WatchKey不再有效");
break;
}
}
}
}
运行效果:
开始监控: D:\2
事件类型: ENTRY_DELETE, 文件: _2025-03-17_13-26-32
文件删除: _2025-03-17_13-26-32
事件类型: ENTRY_CREATE, 文件: 新建文本文档.txt
新文件创建: 新建文本文档.txt
事件类型: ENTRY_DELETE, 文件: 新建文本文档.txt
文件删除: 新建文本文档.txt
事件类型: ENTRY_CREATE, 文件: 1.txt
新文件创建: 1.txt
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
/*
监控指定目录下文件的创建、删除和修改
*/
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.IOException;
/*
监控指定目录(包含子文件夹)下文件的创建、删除和修改
*/
public class RecursiveDirectoryWatcher {
private final WatchService watcher;
private final Path rootDir;
public RecursiveDirectoryWatcher(Path dir) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.rootDir = dir;
// 递归注册所有子目录
registerAllSubdirectories(dir);
}
private void registerAllSubdirectories(final Path start) throws IOException {
Files.walk(start)
.filter(Files::isDirectory)
.forEach(subDir -> {
try {
System.out.println("注册监控目录: " + subDir);
subDir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException e) {
System.err.println("无法注册目录 " + subDir + ": " + e);
}
});
}
public void startWatching() {
System.out.println("开始监控目录树: " + rootDir);
while (true) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
System.err.println("监控被中断");
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(filename);
System.out.printf("事件类型: %s, 文件: %s%n", kind.name(), fullPath);
// 如果是新建目录,则注册监控
if (kind == ENTRY_CREATE && Files.isDirectory(fullPath)) {
try {
registerAllSubdirectories(fullPath);
} catch (IOException e) {
System.err.println("无法注册新目录 " + fullPath + ": " + e);
}
}
}
if (!key.reset()) {
System.out.println("WatchKey不再有效");
break;
}
}
}
public static void main(String[] args) throws IOException {
Path dir = Paths.get("D:/2"); // 替换为你要监控的目录
new RecursiveDirectoryWatcher(dir).startWatching();
}
}
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.IOException;
/*
监控指定目录(指定类型文件)文件的创建、删除和修改
*/
public class TxtFileWatcher {
private final WatchService watcher;
private final Path dir;
public TxtFileWatcher(Path dir) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.dir = dir;
// 注册监控目录
dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
public void startWatching() {
System.out.println("开始监控.txt文件变化,目录: " + dir);
while (true) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
System.err.println("监控被中断");
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
// 只处理.txt文件
if (filename.toString().endsWith(".txt")) {
Path fullPath = dir.resolve(filename);
System.out.printf("事件类型: %s, 文件: %s%n", kind.name(), fullPath);
// 这里可以添加对.txt文件的特定处理逻辑
if (kind == ENTRY_CREATE) {
System.out.println("新的.txt文件创建: " + fullPath);
} else if (kind == ENTRY_MODIFY) {
System.out.println("txt文件被修改: " + fullPath);
// 可以在这里读取文件内容等操作
} else if (kind == ENTRY_DELETE) {
System.out.println("txt文件被删除: " + fullPath);
}
}
}
if (!key.reset()) {
System.out.println("WatchKey不再有效");
break;
}
}
}
public static void main(String[] args) throws IOException {
Path dir = Paths.get("D:/2"); // 替换为你要监控的目录
new TxtFileWatcher(dir).startWatching();
}
}