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


75

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


7
Javadoc:“在此发行版中,此路径找到一个存在的目录。” ==>因此答案是“否,您无法注册文件”。然后:“目录已在监视服务中注册,以便可以监视目录中的条目。” ==>因此,注册目录实际上会监视目录条目上的事件,而不是目录本身上的事件。事件的名称提醒了它们的相关性,它们以ENTRY_开头,例如“ ENTRY_MODIFY-目录中的条目已被修改”。所选答案提供了使用事件的详细信息。
2014年

Answers:


100

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

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

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


3
+1,太好了,谢谢!还有其他有效的方法来观看单个文件吗?
ADJ 2014年

3
不要忘记测试事件是否为OVERFLOW。您不需要注册。这里的例子。
Venkata Raju

3
这里不言而喻的陷阱是,某些平台的监视程序实现可以锁定放置监视程序的目录。
Trejkaz

8
最终的WatchKey watchKey-这是做什么用的?以后似乎不会使用此变量。
mvmn

1
确保您不要尝试在资源目录中查找文件。感觉像它在工作,但不是因为文件更改,而是资源不会自动重新加载。
尼克,

21

不,无法注册文件,监视服务无法以这种方式工作。但是注册目录实际上会监视子目录(文件和子目录)上的更改,而不是目录本身上的更改。

如果要观看文件,请在观看服务中注册包含目录。Path.register()文档说:

WatchKey java.nio.file.Path.register(WatchService watcher,Kind []事件,Modifier ...修饰符)引发IOException

用监视服务注册此路径下的文件。

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

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


20

其他答案是正确的,您必须监视目录并筛选特定文件。但是,您可能希望线程在后台运行。接受的答案可以无限期地阻止,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
        }
    }
}

我尝试从公认的答案和本文中进行工作。您应该能够与此线程一起使用new FileWatcher(new File("/home/me/myfile")).start()并通过调用该线程来停止它stopThread()


2
请记住,非守护程序线程会阻止JVM退出,因此请setDaemon(boolean)在调用之前run()根据您希望应用程序的行为进行。
克里斯H.19年

万一有人两次收到修改事件,请使用以下变通办法解决此问题:stackoverflow.com/q/16777869
Jehad Nasser

10

Apache提供了带有方法的FileWatchDogdoOnChange

private class SomeWatchFile extends FileWatchdog {

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

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

}

在任何需要的地方都可以启动此线程:

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

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


9
知道FileWatchdog类来自哪个库会很高兴?
szydan 2014年

1
从log4j-org.apache.log4j.helpers
idog

8

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

这是我的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();
}

要使用此方法,您只需要扩展并实现如下onModified()方法:

import java.io.File;

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

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

最后,开始查看文件:

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

clean working code! explanatory and well-written answer!
Minhas Kamal


5

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

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.

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.