WatchKey始终为null

问题描述 投票:2回答:2

我尝试查看某些文件以进行更改。但是我从WatchKey得到的watch_object.watch_service.poll(16, TimeUnit.MILLISECONDS);总是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();
        }
    }

}

要运行它:

public static void main(String[] args) {
    // the obj is WIP, just use null for now
    watch(null, "/Users/doekewartena/Desktop/test_image.png");
}
java watch
2个回答
6
投票

我想通过说WatchService高度依赖于实现来为这个答案添加前缀:

平台依赖

观察来自文件系统的事件的实现旨在直接映射到可用的本机文件事件通知工具,或者在本机工具不可用时使用原始机制(例如轮询)。因此,关于如何检测事件,及时性以及是否保留其排序的许多细节都是高度特定于实现的。例如,当修改监视目录中的文件时,它可能在某些实现中导致单个ENTRY_MODIFY事件,但在其他实现中会导致多个事件。短期文件(意味着在创建文件后很快删除的文件)可能无法被定期轮询文件系统以检测更改的原始实现检测到。

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


你提到WatchService.poll总是返回null。这并不奇怪,因为如果没有事件处理标准的队列式行为,poll()poll(long,TimeUnit)将返回null。但是你说你总是得到null,即使你修改了观察文件*。不幸的是,我无法使用OpenJDK 11.0.2(或JDK 1.8.0_202),Windows 10和本地存储设备重现该问题。

*在他们被清理之前,在问题评论中说了这个。

在尝试你的代码时,我观察到一个B被打印到控制台。当然,不容易看到它打印A每个16毫秒是压倒性的,但它就在那里。但是,有一个问题是,在第一次修改事件之后,它将不再报告。这让我对你的代码有一些评论。

  1. 你不打电话给WatchKey.reset()

完成处理WatchKey时调用此方法很重要。该方法标记WatchKey准备好检测新事件。没有这个电话,您将不会观察后续事件。

  1. 你没有poll the eventsWatchKey

为了解决看不到后续事件的问题,我天真地添加了对reset()的调用而没有做任何其他事情。这导致了绝大多数的Bs被打印到控制台。我很困惑,因为我只修改了一次文件,但后来我读了WatchKey.reset(强调我的)的文档:

重置此监视键。

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

我所看到的只是一遍又一遍的同一事件,因为我从来没有处理它。在添加了对WatchEvent.pollEvents()的调用后,我不再被Bs垃圾邮件。

  1. 您为要观看的每个文件创建一个新的WatchService

您似乎想要一个可以观看任意数量文件(只有那些文件)的类。这不需要每个文件WatchService,因为您可以使用相同的WatchService注册多个目录。如果文件来自不同的WatchServices,则需要使用多个FileSystems的情况。但是,您的代码始终使用default file system

使用相同的WatchService也无需使用poll。我假设你使用poll的原因目前是因为你需要检查每个WatchService。因为现在只有一个你可以使用阻塞WatchService.take()方法。


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

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();
        }

    }

}

以及使用上述FileWatcher的小应用程序:

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;
            }
        }
    }

}

并且它的GIF在行动:

enter image description here


0
投票

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

 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;
      }
    });
  }
© www.soinside.com 2019 - 2024. All rights reserved.