Java WatchKey总是空的

Java WatchKey总是空的,java,watch,Java,Watch,我试图观察某些文件的变化。 但是我从watch\u object.watch\u service.poll(16,TimeUnit.ms)获得的WatchKey总是null。 没有一个错误被打印到控制台上,所以我有点不知所措 public class FileWatcher implements Runnable { public FileWatcher() { } static public class Watch_Object { public F

我试图观察某些文件的变化。 但是我从
watch\u object.watch\u service.poll(16,TimeUnit.ms)获得的
WatchKey
总是
null
。 没有一个错误被打印到控制台上,所以我有点不知所措

public class FileWatcher implements Runnable {

    public FileWatcher() {
    }

    static public class Watch_Object {
        public File file;
        public WatchService watch_service;
    }

    static public HashMap<Object, Watch_Object> watched_files = new HashMap<>();

    static public boolean is_running = false;


    static public synchronized void watch(Object obj, String filename) {

        File file = new File(filename);

        if (file.exists()) {

            try {

                WatchService watcher = null;
                watcher = FileSystems.getDefault().newWatchService();

                Watch_Object watch_object = new Watch_Object();
                watch_object.file = file;
                watch_object.watch_service = watcher;

                watched_files.put(obj, watch_object); 

                Path path = file.toPath().getParent();
                path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);

                if (!is_running) {
                    (new Thread(new FileWatcher())).start();
                    is_running = true;
                }


            } 
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return;
            }

        }
        else {
            // Error
        }

    }



    @Override
    public void run() {

        try  {
            while (true) {
                synchronized(this) {

                    for (Watch_Object watch_object : watched_files.values()) {

                        WatchKey key = watch_object.watch_service.poll(16, TimeUnit.MILLISECONDS);

                        System.out.println("A");

                        if (key != null) {

                            System.out.println("B");

                        }

                    }

                }
                Thread.sleep(16);    
            }
        } 
        catch (Throwable e) {
            // Log or rethrow the error
            e.printStackTrace();
        }
    }

}

我想在回答这个问题之前说,它高度依赖于实现:

平台依赖关系 观察来自文件系统的事件的实现旨在直接映射到本机文件事件通知设施(如果可用),或者在本机设施不可用时使用原始机制(如轮询)。因此,关于如何检测事件、事件的及时性以及事件的顺序是否得到保留的许多细节都是与实现高度相关的。例如,当修改监视目录中的文件时,在某些实现中可能会导致一个
ENTRY\u MODIFY
事件,但在其他实现中可能会导致多个事件。短寿命文件(即创建后很快删除的文件)可能不会被定期轮询文件系统以检测更改的基本实现检测到

如果监视的文件不在本地存储设备上,则如果可以检测到对该文件的更改,则这是特定于实现的。特别是,不要求检测在远程系统上执行的文件更改


您提到了
WatchService.poll
总是返回
null
。这并不完全令人惊讶,因为如果没有处理标准队列行为的事件,and和将返回
null
。但是您说您总是得到
null
,即使您修改了监视的文件*。不幸的是,我无法使用OpenJDK 11.0.2(或JDK 1.8.0_202)、Windows 10和本地存储设备重现该问题

*这是在他们被清理之前的问题评论中说的

在尝试代码时,我发现控制台上打印出了一个
B
。诚然,很难将其视为每隔
16
毫秒打印一次
A
,这是压倒性的,但它确实存在。不过,有一个问题是,在第一次修改事件之后,它将不再报告。这使我对您的代码有了一些看法

  • 你不打电话
  • 处理完
    WatchKey
    后调用此方法非常重要。该方法将
    WatchKey
    标记为准备检测新事件。没有此呼叫,您将无法观察后续事件

  • 您不需要使用
    监视键
  • 为了解决看不到后续事件的问题,我天真地添加了一个对
    reset()
    的调用,而没有做任何其他事情。这导致大量的
    B
    s被打印到控制台。我很困惑,因为我只修改了一次文件,但后来我阅读了
    WatchKey.reset
    emphasis我的)的文档:

    重置此手表键

    如果此监视键已被取消或此监视键已处于就绪状态,则调用此方法无效否则,如果对象存在挂起事件,则此监视密钥将立即重新排队到监视服务。如果没有挂起事件,则监视密钥将进入就绪状态,并将保持该状态,直到检测到事件或取消监视密钥

    我所看到的只是一次又一次的相同事件,因为我从未处理过它。在给我添加了一个电话后,我不再被垃圾短信
    B
    s

  • 您可以为要查看的每个文件创建一个新的
    WatchService
  • 您似乎想要一个可以监视任意数量的文件(并且只监视这些文件)的类。这不需要每个文件都使用
    WatchService
    ,因为您可以使用相同的
    WatchService
    注册多个目录。如果文件来自不同的
    文件系统,则需要使用多个
    WatchService
    s。但是,您的代码始终使用

    使用相同的
    WatchService
    也无需使用
    poll
    。我假设您当前使用
    poll
    的原因是因为您需要检查每个
    WatchService
    。因为现在只有一种方法,所以可以改用阻塞方法


    这里有一个小例子,我相信,你想做什么就做什么。我不能保证它是完美的,因为它没有经过彻底的测试。我也不能保证它能在你的电脑上工作

    import java.io.Closeable;
    import java.io.IOException;
    import java.nio.file.ClosedWatchServiceException;
    import java.nio.file.FileSystem;
    import java.nio.file.Path;
    import java.nio.file.WatchEvent;
    import java.nio.file.WatchKey;
    import java.nio.file.WatchService;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Set;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.function.BiConsumer;
    
    /**
     * Watches files for modification events, but not for creation, 
     * deletion, or overflow events.
     */
    public class FileWatcher implements Closeable, Runnable {
    
        private final List<BiConsumer<? super FileWatcher, ? super Path>> handlers 
                = new CopyOnWriteArrayList<>();
    
        private final Object lock = new Object();
        private final Map<Path, Registry> registeredDirs = new HashMap<>();
        private final Set<Path> watchedFiles = new HashSet<>();
    
        private final AtomicBoolean running = new AtomicBoolean();
        private final FileSystem fileSystem;
        private final WatchService service;
    
        public FileWatcher(FileSystem fs) throws IOException {
            service = fs.newWatchService();
            fileSystem = fs;
        }
    
        public FileSystem getFileSystem() {
            return fileSystem;
        }
    
        public boolean startWatching(Path file) throws IOException {
            Objects.requireNonNull(file);
            synchronized (lock) {
                if (watchedFiles.add(file)) {
                    Path directory = file.getParent();
                    if (registeredDirs.containsKey(directory)) {
                        registeredDirs.get(directory).incrementCount();
                    } else {
                        try {
                            WatchKey key = directory.register(service, ENTRY_MODIFY);
                            registeredDirs.put(directory, new Registry(key));
                        } catch (ClosedWatchServiceException | IllegalArgumentException
                                | IOException | SecurityException ex) {
                            watchedFiles.remove(file);
                            throw ex;
                        }
                    }
                    return true;
                }
                return false;
            }
        }
    
        public boolean stopWatching(Path file) {
            Objects.requireNonNull(file);
            synchronized (lock) {
                if (watchedFiles.remove(file)) {
                    Path directory = file.getParent();
                    Registry registry = registeredDirs.get(directory);
                    if (registry.decrementCount()) {
                        registeredDirs.remove(directory);
                        registry.cancelKey();
                    }
                    return true;
                }
                return false;
            }
        }
    
        public void addHandler(BiConsumer<? super FileWatcher, ? super Path> handler) {
            handlers.add(Objects.requireNonNull(handler));
        }
    
        public void removeHandler(BiConsumer<? super FileWatcher, ? super Path> handler) {
            handlers.remove(Objects.requireNonNull(handler));
        }
    
        private void fireModifyEvent(Path source) {
            for (BiConsumer<? super FileWatcher, ? super Path> handler : handlers) {
                try {
                    handler.accept(this, source);
                } catch (RuntimeException ex) {
                    Thread.currentThread().getUncaughtExceptionHandler()
                            .uncaughtException(Thread.currentThread(), ex);
                }
            }
        }
    
        @Override
        public void close() throws IOException {
            service.close();
            synchronized (lock) {
                registeredDirs.clear();
                watchedFiles.clear();
            }
        }
    
        @Override
        public void run() {
            if (running.compareAndSet(false, true)) {
                try {
                    while (!Thread.interrupted()) {
                        WatchKey key = service.take();
                        for (WatchEvent<?> event : key.pollEvents()) {
                            Path source = ((Path) key.watchable())
                                    .resolve((Path) event.context());
                            boolean isWatched;
                            synchronized (lock) {
                                isWatched = watchedFiles.contains(source);
                            }
                            if (isWatched) {
                                fireModifyEvent(source);
                            }
                        }
                        key.reset();
                    }
                } catch (InterruptedException ignore) {
                } finally {
                    running.set(false);
                }
            } else {
                throw new IllegalStateException("already running");
            }
        }
    
        private static class Registry {
    
            private final WatchKey key;
            private int count;
    
            private Registry(WatchKey key) {
                this.key = key;
                incrementCount();
            }
    
            private void incrementCount() {
                count++;
            }
    
            private boolean decrementCount() {
                return --count <= 0;
            }
    
            private void cancelKey() {
                key.cancel();
            }
    
        }
    
    }
    
    和一个正在运行的GIF:


    这是我正在使用的工作代码,它与您的代码非常相似。我希望这有帮助

     public void watchDirectory(Path dir) {
        logger.info("Watching directory {} for new Files", dir);
    
        WatchService watchService = FileSystems.getDefault().newWatchService();
        registerRecursive(dir, watchService);
        WatchKey key;
        while ((key = watchService.take()) != null) {
          key.pollEvents();
          executorService.submit(this::performAction);
          boolean reset = key.reset();
          if (!reset) {
            logger.error("Could not reset the WatchKey");
            throw new RunTimeException("Could not reset the WatchKey");
          }
        }
    
      }
    
      private void performAction() {
        // Your code after an event is registered
      }
    
      private void registerRecursive(Path root, WatchService watchService) throws IOException {
        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
    
          @Override
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
            return FileVisitResult.CONTINUE;
          }
        });
      }
    
    public-void-watchDirectory(路径目录){
    info(“监视目录{}查找新文件”,dir);
    WatchService WatchService=FileSystems.getDefault().newWatchService();
    registerRecursive(dir,watchService);
    监视键;
    while((key=watchService.take())!=null){
    key.pollEvents();
    executorService.submit(此::performAction);
    布尔重置=key.reset();
    如果(!重置){
    logger.error(“无法重置监视键”);
    抛出新的RunTimeException(“无法重置WatchKey”);
    }
    }
    }
    私有无效性能(){
    //事件注册后的代码
    }
    私有void registerRecursive(路径根,WatchService WatchService)引发IOException{
    walkFileTree(根目录,新的SimpleFileVisitor(){
    @凌驾
    公共文件VisitResult preVisitDirectory(路径目录,基本文件属性属性属性)引发IOException{
    目录注册(watchService、StandardWatchEventTypes.ENTRY\u创建、StandardWatchEventTypes.ENTRY\u删除、StandardWatchEventTypes.ENTRY\u修改);
    返回FileVisitResult.CONTINUE;
    }
    });
    }
    
    import java.io.IOException;
    import java.nio.file.FileSystems;
    import java.nio.file.Files;
    import java.nio.file.LinkOption;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.Scanner;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Main {
    
        public static void main(String[] args) throws IOException {
            Path file = chooseFile();
            if (file == null) {
                return;
            }
            System.out.println("Entered \"" + file + "\"");
    
            ExecutorService executor = Executors.newSingleThreadExecutor();
            try (FileWatcher watcher = new FileWatcher(FileSystems.getDefault())) {
                Future<?> task = executor.submit(watcher);
                executor.shutdown();
    
                watcher.addHandler((fw, path) -> System.out.println("File modified: " + path));
    
                watcher.startWatching(file);
    
                waitForExit();
                task.cancel(true);
            } finally {
                executor.shutdownNow();
            }
        }
    
        private static Path chooseFile() {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("Enter file (or 'exit' to exit application): ");
                String line = scanner.nextLine();
                if ("exit".equalsIgnoreCase(line.trim())) {
                    return null;
                }
                Path file = Paths.get(line).toAbsolutePath().normalize();
                if (Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
                    return file;
                }
                System.out.println("File must exist and be a regular file. Try again.");
            }
        }
    
        private static void waitForExit() {
            System.out.println("\nType 'exit' to exit the application.");
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if ("exit".equalsIgnoreCase(line.trim())) {
                    return;
                }
            }
        }
    
    }
    
     public void watchDirectory(Path dir) {
        logger.info("Watching directory {} for new Files", dir);
    
        WatchService watchService = FileSystems.getDefault().newWatchService();
        registerRecursive(dir, watchService);
        WatchKey key;
        while ((key = watchService.take()) != null) {
          key.pollEvents();
          executorService.submit(this::performAction);
          boolean reset = key.reset();
          if (!reset) {
            logger.error("Could not reset the WatchKey");
            throw new RunTimeException("Could not reset the WatchKey");
          }
        }
    
      }
    
      private void performAction() {
        // Your code after an event is registered
      }
    
      private void registerRecursive(Path root, WatchService watchService) throws IOException {
        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
    
          @Override
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
            return FileVisitResult.CONTINUE;
          }
        });
      }