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();
    }
}
相关推荐
小钊(求职中)1 分钟前
七种分布式ID生成方式详细介绍--Redis、雪花算法、号段模式以及美团Leaf 等
java·spring boot·分布式·spring·mybatis
martian6656 分钟前
分布式并发控制实战手册:从Redis锁到ZK选主的架构之道
java·开发语言·redis·分布式·架构
西岭千秋雪_6 分钟前
Spring Boot自动配置原理解析
java·spring boot·后端·spring·springboot
日暮南城故里21 分钟前
常用的排序算法------练习4
java·数据结构·算法
敖正炀34 分钟前
CountDownLatch详解
java
敖正炀35 分钟前
Java 并发工具解析
java
SimonKing37 分钟前
JDK 24 新特性解析:更安全、更高效、更易用
java·后端·架构
敖正炀37 分钟前
CyclicBarrier详解
java
敖正炀38 分钟前
Java 8 中的 ConcurrentHashMap 详解
java
敖正炀39 分钟前
Exchanger详解
java