Java 我可以使用 WatchService(不是整个目录)监视单个文件的更改吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16251273/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 06:01:19  来源:igfitidea点击:

Can I watch for single file change with WatchService (not the whole directory)?

javawatchservice

提问by fedor.belov

When I'm trying to register a file instead of a directory java.nio.file.NotDirectoryExceptionis thrown. Can I listen for a single file change, not the whole directory?

当我尝试注册一个文件而不是一个目录时会java.nio.file.NotDirectoryException被抛出。我可以监听单个文件的更改,而不是整个目录吗?

采纳答案by Boris the Spider

Just filter the events for the file you want in the directory:

只需过滤目录中所需文件的事件:

final Path path = FileSystems.getDefault().getPath(System.getProperty("user.home"), "Desktop");
System.out.println(path);
try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
    final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
    while (true) {
        final WatchKey wk = watchService.take();
        for (WatchEvent<?> event : wk.pollEvents()) {
            //we only register "ENTRY_MODIFY" so the context is always a Path.
            final Path changed = (Path) event.context();
            System.out.println(changed);
            if (changed.endsWith("myFile.txt")) {
                System.out.println("My file has changed");
            }
        }
        // reset the key
        boolean valid = wk.reset();
        if (!valid) {
            System.out.println("Key has been unregisterede");
        }
    }
}

Here we check whether the changed file is "myFile.txt", if it is then do whatever.

这里我们检查更改后的文件是否为“myFile.txt”,如果是则执行任何操作。

回答by idog

Apache offers a FileWatchDogclass with a doOnChangemethod.

Apache 提供了一个带有方法的FileWatchDogdoOnChange

private class SomeWatchFile extends FileWatchdog {

    protected SomeWatchFile(String filename) {
        super(filename);
    }

    @Override
    protected void doOnChange() {
        fileChanged= true;
    }

}

And where ever you want you can start this thread:

你可以在任何地方开始这个线程:

SomeWatchFile someWatchFile = new SomeWatchFile (path);
someWatchFile.start();

The FileWatchDog class polls a file's lastModified()timestamp. The native WatchService from Java NIO is more efficient, since notifications are immediate.

FileWatchDog 类轮询文件的lastModified()时间戳。Java NIO 的原生 WatchService 效率更高,因为通知是即时的。

回答by mins

No it isn't possible to register a file, the watch service doesn't work this way. But registeringa directory actually watcheschanges on the directory children (the files and sub-directories), not the changes on the directory itself.

不,不可能注册文件,监视服务不能以这种方式工作。但是注册目录实际上会观察子目录(文件和子目录)的变化,而不是目录本身的变化。

If you want to watch a file, then you register the containing directory with the watch service. Path.register() documentationsays:

如果你想观看一个文件,那么你可以使用 watch 服务注册包含目录。Path.register() 文档说:

WatchKey java.nio.file.Path.register(WatchService watcher, Kind[] events, Modifier... modifiers) throws IOException

Registers the file located by this path with a watch service.

In this release, this path locates a directory that exists. The directory is registeredwith the watch service so that entries in the directory can be watched

WatchKey java.nio.file.Path.register(WatchService watcher, Kind[] events, Modifier... modifiers) 抛出 IOException

向监视服务注册此路径所在的文件。

在此版本中,此路径定位存在的目录。该目录已注册到监视服务,以便可以监视目录中的条目

Then you need to process events on entries, and detect those related to the file you are interested in, by checking the context value of the event. The context value represents the name of the entry (actually the path of the entry relatively to the path of its parent, which is exactly the child name). You have an example here.

然后,您需要处理条目上的事件,并通过检查事件的上下文值来检测与您感兴趣的文件相关的事件。上下文值表示条目的名称(实际上是条目相对于其父路径的路径,也就是子名称)。你有一个例子here

回答by timrs2998

Other answers are right that you must watch a directory and filter for your particular file. However, you probably want a thread running in the background. The accepted answer can block indefinitely on watchService.take();and doesn't close the WatchService. A solution suitable for a separate thread might look like:

其他答案是正确的,您必须监视目录并过滤特定文件。但是,您可能希望在后台运行一个线程。接受的答案可以无限期地阻止watchService.take();并且不会关闭 WatchService。适用于单独线程的解决方案可能如下所示:

public class FileWatcher extends Thread {
    private final File file;
    private AtomicBoolean stop = new AtomicBoolean(false);

    public FileWatcher(File file) {
        this.file = file;
    }

    public boolean isStopped() { return stop.get(); }
    public void stopThread() { stop.set(true); }

    public void doOnChange() {
        // Do whatever action you want here
    }

    @Override
    public void run() {
        try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
            Path path = file.toPath().getParent();
            path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
            while (!isStopped()) {
                WatchKey key;
                try { key = watcher.poll(25, TimeUnit.MILLISECONDS); }
                catch (InterruptedException e) { return; }
                if (key == null) { Thread.yield(); continue; }

                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();

                    @SuppressWarnings("unchecked")
                    WatchEvent<Path> ev = (WatchEvent<Path>) event;
                    Path filename = ev.context();

                    if (kind == StandardWatchEventKinds.OVERFLOW) {
                        Thread.yield();
                        continue;
                    } else if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
                            && filename.toString().equals(file.getName())) {
                        doOnChange();
                    }
                    boolean valid = key.reset();
                    if (!valid) { break; }
                }
                Thread.yield();
            }
        } catch (Throwable e) {
            // Log or rethrow the error
        }
    }
}

I tried working from the accepted answer and this article. You should be able to use this thread with new FileWatcher(new File("/home/me/myfile")).start()and stop it by calling stopThread()on the thread.

我尝试从接受的答案和这篇文章开始工作。您应该能够使用该线程new FileWatcher(new File("/home/me/myfile")).start()并通过调用该线程来停止它stopThread()

回答by John Rix

Not sure about others, but I groan at the amount of code needed to watch a single file for changes using the basic WatchService API. It has to be simpler!

不确定其他人,但我对使用基本 WatchService API 观察单个文件的更改所需的代码量感到不满。它必须更简单!

Here are a couple of alternatives using third party libraries:

以下是使用第三方库的几种替代方案:

回答by Hindol

I have created a wrapper around Java 1.7's WatchServicethat allows registering a directory and any number of globpatterns. This class will take care of the filtering and only emit events you are interested in.

我已经创建了一个围绕 Java 1.7 的包装器WatchService,它允许注册一个目录和任意数量的glob模式。此类将负责过滤并仅发出您感兴趣的事件。

try {
    DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
    watchService.register( // May throw
            new DirectoryWatchService.OnFileChangeListener() {
                @Override
                public void onFileCreate(String filePath) {
                    // File created
                }

                @Override
                public void onFileModify(String filePath) {
                    // File modified
                }

                @Override
                public void onFileDelete(String filePath) {
                    // File deleted
                }
            },
            <directory>, // Directory to watch
            <file-glob-pattern-1>, // E.g. "*.log"
            <file-glob-pattern-2>, // E.g. "input-?.txt"
            <file-glob-pattern-3>, // E.g. "config.ini"
            ... // As many patterns as you like
    );

    watchService.start(); // The actual watcher runs on a new thread
} catch (IOException e) {
    LOGGER.error("Unable to register file change listener for " + fileName);
}

Complete code is in this repo.

完整的代码在这个repo 中

回答by BullyWiiPlaza

You cannot watch an individual file directly but you can filter out what you don't need.

您不能直接观看单个文件,但可以过滤掉不需要的文件。

Here is my FileWatcherclass implementation:

这是我的FileWatcher类实现:

import java.io.File;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;

import static java.nio.file.StandardWatchEventKinds.*;

public abstract class FileWatcher
{
    private Path folderPath;
    private String watchFile;

    public FileWatcher(String watchFile)
    {
        Path filePath = Paths.get(watchFile);

        boolean isRegularFile = Files.isRegularFile(filePath);

        if (!isRegularFile)
        {
            // Do not allow this to be a folder since we want to watch files
            throw new IllegalArgumentException(watchFile + " is not a regular file");
        }

        // This is always a folder
        folderPath = filePath.getParent();

        // Keep this relative to the watched folder
        this.watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
    }

    public void watchFile() throws Exception
    {
        // We obtain the file system of the Path
        FileSystem fileSystem = folderPath.getFileSystem();

        // We create the new WatchService using the try-with-resources block
        try (WatchService service = fileSystem.newWatchService())
        {
            // We watch for modification events
            folderPath.register(service, ENTRY_MODIFY);

            // Start the infinite polling loop
            while (true)
            {
                // Wait for the next event
                WatchKey watchKey = service.take();

                for (WatchEvent<?> watchEvent : watchKey.pollEvents())
                {
                    // Get the type of the event
                    Kind<?> kind = watchEvent.kind();

                    if (kind == ENTRY_MODIFY)
                    {
                        Path watchEventPath = (Path) watchEvent.context();

                        // Call this if the right file is involved
                        if (watchEventPath.toString().equals(watchFile))
                        {
                            onModified();
                        }
                    }
                }

                if (!watchKey.reset())
                {
                    // Exit if no longer valid
                    break;
                }
            }
        }
    }

    public abstract void onModified();
}

To use this, you just have to extend and implement the onModified()method like so:

要使用它,您只需要onModified()像这样扩展和实现该方法:

import java.io.File;

public class MyFileWatcher extends FileWatcher
{
    public MyFileWatcher(String watchFile)
    {
        super(watchFile);
    }

    @Override
    public void onModified()
    {
        System.out.println("Modified!");
    }
}

Finally, start watching the file:

最后,开始观看文件:

String watchFile = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Test.txt";
FileWatcher fileWatcher = new MyFileWatcher(watchFile);
fileWatcher.watchFile();