如何用Java案例实现文件监控?

wen java案例 5

本文目录导读:

如何用Java案例实现文件监控?

  1. 使用Java NIO的WatchService(推荐)
  2. 增强版:递归监控子目录
  3. 使用Apache Commons IO
  4. 使用Java的File.lastModified()轮询(基础方法)
  5. 实际应用:配置文件监控器
  6. 性能对比和选择建议

我来介绍几种Java实现文件监控的方法,从基础到高级:

使用Java NIO的WatchService(推荐)

这是Java 7+官方推荐的文件监控方式:

import java.io.IOException;
import java.nio.file.*;
import java.util.List;
public class FileWatcherExample {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 监控的目录
        Path dir = Paths.get("/path/to/monitor");
        // 创建WatchService
        WatchService watchService = FileSystems.getDefault().newWatchService();
        // 注册监控事件
        dir.register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);
        System.out.println("开始监控目录: " + dir.toAbsolutePath());
        // 循环处理事件
        while (true) {
            WatchKey key = watchService.take(); // 阻塞等待
            List<WatchEvent<?>> events = key.pollEvents();
            for (WatchEvent<?> event : events) {
                WatchEvent.Kind<?> kind = event.kind();
                Path filename = (Path) event.context();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    continue;
                }
                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("文件创建: " + filename);
                } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("文件删除: " + filename);
                } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("文件修改: " + filename);
                }
            }
            // 重置WatchKey
            boolean valid = key.reset();
            if (!valid) {
                break;
            }
        }
        watchService.close();
    }
}

增强版:递归监控子目录

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
public class RecursiveFileWatcher {
    private final WatchService watchService;
    private final Map<WatchKey, Path> keyPathMap = new HashMap<>();
    public RecursiveFileWatcher() throws IOException {
        this.watchService = FileSystems.getDefault().newWatchService();
    }
    // 递归注册所有子目录
    private void registerDirectory(Path dir) throws IOException {
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                    throws IOException {
                WatchKey key = dir.register(watchService,
                        StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_MODIFY);
                keyPathMap.put(key, dir);
                System.out.println("监控目录: " + dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
    public void startMonitoring(Path rootDir) throws IOException, InterruptedException {
        registerDirectory(rootDir);
        while (true) {
            WatchKey key = watchService.take();
            Path dir = keyPathMap.get(key);
            if (dir == null) continue;
            for (WatchEvent<?> event : key.pollEvents()) {
                WatchEvent.Kind<?> kind = event.kind();
                Path filename = (Path) event.context();
                Path fullPath = dir.resolve(filename);
                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("创建: " + fullPath);
                    // 如果是新创建的目录,也要注册监控
                    if (Files.isDirectory(fullPath)) {
                        registerDirectory(fullPath);
                    }
                } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("删除: " + fullPath);
                } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("修改: " + fullPath);
                }
            }
            if (!key.reset()) {
                keyPathMap.remove(key);
                if (keyPathMap.isEmpty()) break;
            }
        }
    }
    public static void main(String[] args) throws Exception {
        RecursiveFileWatcher watcher = new RecursiveFileWatcher();
        watcher.startMonitoring(Paths.get("/path/to/monitor"));
    }
}

使用Apache Commons IO

需要添加依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import java.io.File;
public class CommonsIOFileWatcher {
    public static void main(String[] args) throws Exception {
        // 监控目录
        File directory = new File("/path/to/monitor");
        // 创建文件观察器
        FileAlterationObserver observer = new FileAlterationObserver(directory);
        // 添加事件监听器
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                System.out.println("文件创建: " + file.getAbsolutePath());
            }
            @Override
            public void onFileDelete(File file) {
                System.out.println("文件删除: " + file.getAbsolutePath());
            }
            @Override
            public void onFileChange(File file) {
                System.out.println("文件修改: " + file.getAbsolutePath());
            }
            @Override
            public void onDirectoryCreate(File dir) {
                System.out.println("目录创建: " + dir.getAbsolutePath());
            }
            @Override
            public void onDirectoryDelete(File dir) {
                System.out.println("目录删除: " + dir.getAbsolutePath());
            }
        });
        // 创建监控器,设置检查间隔(毫秒)
        FileAlterationMonitor monitor = new FileAlterationMonitor(1000, observer);
        // 启动监控
        monitor.start();
        System.out.println("开始监控...");
        // 运行一段时间后停止
        Thread.sleep(60000);
        monitor.stop();
    }
}

使用Java的File.lastModified()轮询(基础方法)

import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class PollingFileWatcher {
    private final Map<String, Long> fileLastModifiedMap = new HashMap<>();
    private volatile boolean running = true;
    public void startMonitoring(String directoryPath, long interval) {
        File directory = new File(directoryPath);
        new Thread(() -> {
            while (running) {
                try {
                    checkDirectory(directory);
                    Thread.sleep(interval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "File-Watcher").start();
    }
    private void checkDirectory(File directory) {
        File[] files = directory.listFiles();
        if (files == null) return;
        for (File file : files) {
            String path = file.getAbsolutePath();
            long lastModified = file.lastModified();
            Long previousModified = fileLastModifiedMap.get(path);
            if (previousModified == null) {
                // 新文件
                System.out.println("新文件: " + path);
                fileLastModifiedMap.put(path, lastModified);
            } else if (previousModified != lastModified) {
                // 文件被修改
                System.out.println("文件修改: " + path);
                fileLastModifiedMap.put(path, lastModified);
            }
        }
        // 检查是否有文件被删除
        fileLastModifiedMap.keySet().removeIf(path -> !new File(path).exists());
    }
    public void stop() {
        this.running = false;
    }
    public static void main(String[] args) throws InterruptedException {
        PollingFileWatcher watcher = new PollingFileWatcher();
        watcher.startMonitoring("/path/to/monitor", 1000); // 每秒检查一次
        Thread.sleep(60000);
        watcher.stop();
    }
}

实际应用:配置文件监控器

import java.io.IOException;
import java.nio.file.*;
import java.util.function.Consumer;
public class ConfigFileWatcher {
    private final Path configPath;
    private final Consumer<Path> onFileChange;
    private Thread watchThread;
    public ConfigFileWatcher(String configFilePath, Consumer<Path> onFileChange) {
        this.configPath = Paths.get(configFilePath).toAbsolutePath();
        this.onFileChange = onFileChange;
    }
    public void start() throws IOException {
        Path parentDir = configPath.getParent();
        if (!Files.exists(parentDir)) {
            Files.createDirectories(parentDir);
        }
        watchThread = new Thread(() -> {
            try {
                WatchService watchService = FileSystems.getDefault().newWatchService();
                parentDir.register(watchService,
                        StandardWatchEventKinds.ENTRY_MODIFY);
                System.out.println("开始监控配置文件: " + configPath);
                while (!Thread.currentThread().isInterrupted()) {
                    WatchKey key = watchService.take();
                    for (WatchEvent<?> event : key.pollEvents()) {
                        Path changedFile = (Path) event.context();
                        Path fullPath = parentDir.resolve(changedFile);
                        if (fullPath.equals(configPath)) {
                            System.out.println("配置文件已修改: " + configPath);
                            onFileChange.accept(configPath);
                        }
                    }
                    if (!key.reset()) break;
                }
                watchService.close();
            } catch (IOException | InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Config-Watcher");
        watchThread.setDaemon(true);
        watchThread.start();
    }
    public void stop() {
        if (watchThread != null) {
            watchThread.interrupt();
        }
    }
    // 使用示例
    public static void main(String[] args) throws IOException {
        ConfigFileWatcher watcher = new ConfigFileWatcher(
            "/etc/myapp/config.properties",
            path -> {
                System.out.println("重新加载配置...");
                // 重新加载配置文件的逻辑
            }
        );
        watcher.start();
        // 添加关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(watcher::stop));
    }
}

性能对比和选择建议

方法 优点 缺点 适用场景
WatchService 实时性高,资源消耗低 部分系统支持有限 大多数文件监控需求
Apache Commons IO 功能完善,易用性好 需要外部依赖 需要高级功能的项目
轮询方式 简单直接,兼容性好 延迟高,资源消耗大 简单场景或旧系统

最佳实践建议:

  • 首选使用Java NIO的WatchService
  • 对于生产环境,考虑使用成熟的框架如Apache Commons IO或JNotify
  • 监控配置文件的变更是最常见的应用场景
  • 注意处理WatchEvent.OVERFLOW事件,表示可能丢失事件

这些示例涵盖了文件监控的主要实现方式,你可以根据具体需求选择合适的方案。

抱歉,评论功能暂时关闭!