Java WatchService监控指定路径下的文件新增、删除和修改(子文件夹、指定文件类型)

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();
    }
}
相关推荐
京东云开发者7 分钟前
Java的SPI机制详解
java
超级小忍29 分钟前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
程序无bug33 分钟前
Spring IoC注解式开发无敌详细(细节丰富)
java·后端
小莫分享35 分钟前
Java Lombok 入门
java
程序无bug35 分钟前
Spring 对于事务上的应用的详细说明
java·后端
食亨技术团队37 分钟前
被忽略的 SAAS 生命线:操作日志有多重要
java·后端
苦学编程的谢1 小时前
Maven
java·maven·intellij-idea
考虑考虑1 小时前
Maven 依赖范围(Scope)
java·后端·maven
张小洛1 小时前
Spring AOP 设计解密:代理对象生成、拦截器链调度与注解适配全流程源码解析
java·后端·spring·spring aop·aop
Wyc724092 小时前
SpringBoot
java·spring boot·spring