Java WatchService案例

wen java案例 7

本文目录导读:

Java WatchService案例

  1. 基础WatchService案例
  2. 增强版:递归目录监控
  3. 实用案例:配置文件热更新监听器
  4. 文件过滤监控器
  5. 使用建议

我将为您提供一个完整的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();
    }
}

使用建议

  1. 性能考虑:WatchService是事件驱动的,但需要注意:

    • 高频率修改事件可能导致性能问题
    • 使用OVERFLOW事件处理事件丢失情况
    • 考虑事件积压和线程池配置
  2. 可靠性

    • 确保正确调用key.reset()以继续接收事件
    • 处理无效的WatchKey(目录被删除等)
    • 考虑使用异常处理和重试机制
  3. 适用场景

    • 配置文件热更新
    • 日志文件监控
    • 上传文件自动处理
    • 目录同步工具

这些案例覆盖了Java WatchService的主要使用场景,可以根据实际需求选择合适的实现。

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