WatchKey始终为null - java

我尝试查看某些文件的更改。
但是我从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");
}

参考方案

我想在此前面加上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十分麻烦,要看到它并不容易,但它确实在那里。但是,有一个问题,在第一次修改事件之后,它将不再报告。这使我对您的代码有几点评论。

您不拨打WatchKey.reset()

处理完WatchKey后,调用此方法很重要。该方法将WatchKey标记为准备检测新事件。没有此呼叫,您将不会观察到后续事件。

您不是WatchKey的poll the events。

为了解决未看到的后续事件的问题,我天真地添加了对reset()的调用,而没有进行任何其他操作。这导致大量B打印到控制台。我很困惑,因为我只修改了一次文件,但是随后我阅读了WatchKey.reset(强调我的)文档:

重置此监视键。

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

我看到的只是一次又一次的同一事件,因为我从未处理过它。向WatchEvent.pollEvents()添加呼叫后,我不再受到B的垃圾邮件攻击。

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

您似乎想要一个可以监视任意数量的文件(并且仅监视那些文件)的类。这不需要每个文件WatchService,因为您可以使用同一WatchService注册多个目录。如果文件来自不同的WatchService,则可能需要使用多个FileSystem。但是,您的代码始终使用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效果:

WatchKey始终为null - java

对于Java中的isDirectory和isFile,文件始终返回false - java

为什么file为isFile()方法返回false,即使它是file。当它是目录时,它为isDirectory()返回false。难道我做错了什么?我测试的这些文件/目录不存在,我需要创建它们,所以这就是为什么我要测试使用createFile()还是mkdir()的原因。File file = new File("C:/Users/John/Des…

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…

Java Double与BigDecimal - java

我正在查看一些使用双精度变量来存储(360-359.9998779296875)结果为0.0001220703125的代码。 double变量将其存储为-1.220703125E-4。当我使用BigDecimal时,其存储为0.0001220703125。为什么将它双重存储为-1.220703125E-4? 参考方案 我不会在这里提及精度问题,而只会提及数字…