我是否可以使用 WatchService (而不是整个目录)查看单个文件的更改?

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

89362 次浏览

只要筛选目录中所需文件的事件即可:

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”,如果是,则执行任何操作。

Apache 提供了一个带有 doOnChange方法的 文件监视狗类。

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()时间戳。来自 JavaNIO 的本机 WatchService 效率更高,因为通知是即时的。

不可能注册一个文件,手表服务不是这样工作的。但是 登记一个目录实际上 手表更改的是目录子目录(文件和子目录) ,而不是目录本身的更改。

如果你想要监视一个文件,那么你需要向监视服务注册包含该文件的目录。 Register ()文档说:

注册表(WatchService 观察器,类型[]事件,修改器..。 抛出 IOException

向监视服务注册由此路径定位的文件。

在这个版本中,此路径定位到一个存在的目录

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

其他答案是正确的,即您必须监视特定文件的目录和筛选器。但是,您可能希望在后台运行一个线程。接受的答案可以在 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()来停止它。

其他的我不太清楚,但是我对使用基本的 WatchServiceAPI 来监视单个文件的更改所需的代码量感到不满。一定要简单点!

下面是一些使用第三方库的替代方案:

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

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

完整的代码在这个 回购中。

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

下面是我的 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();

我通过 BullyWiiplaza 对解决方案进行了一些扩展,以便与 javafx.concurrent集成,例如 javafx.concurrent.Taskjavafx.concurrent.Service。 我还增加了追踪多个文件的可能性。 任务:

import javafx.concurrent.Task;
import lombok.extern.slf4j.Slf4j;


import java.io.File;
import java.nio.file.*;
import java.util.*;


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


@Slf4j
public abstract class FileWatcherTask extends Task<Void> {


static class Entry {
private final Path folderPath;
private final String watchFile;


Entry(Path folderPath, String watchFile) {
this.folderPath = folderPath;
this.watchFile = watchFile;
}


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(folderPath, entry.folderPath) && Objects.equals(watchFile, entry.watchFile);
}


@Override
public int hashCode() {
return Objects.hash(folderPath, watchFile);
}
}


private final List<Entry> entryList;


private final Map<WatchKey, Entry> watchKeyEntryMap;


public FileWatcherTask(Iterable<String> watchFiles) {
this.entryList = new ArrayList<>();
this.watchKeyEntryMap = new LinkedHashMap<>();
for (String watchFile : watchFiles) {
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
Path folderPath = filePath.getParent();
// Keep this relative to the watched folder
watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
Entry entry = new Entry(folderPath, watchFile);
entryList.add(entry);
log.debug("Watcher initialized for {} entries. ({})", entryList.size(), entryList.stream().map(e -> e.watchFile + "-" + e.folderPath).findFirst().orElse("<>"));
}
}


public FileWatcherTask(String... watchFiles) {
this(Arrays.asList(watchFiles));
}


public void watchFile() throws Exception {
// We obtain the file system of the Path
// FileSystem fileSystem = folderPath.getFileSystem();
// TODO: use the actual file system instead of default
FileSystem fileSystem = FileSystems.getDefault();


// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService()) {
log.debug("Watching filesystem {}", fileSystem);
for (Entry e : entryList) {
// We watch for modification events
WatchKey key = e.folderPath.register(service, ENTRY_MODIFY);
watchKeyEntryMap.put(key, e);
}


// Start the infinite polling loop
while (true) {
// Wait for the next event
WatchKey watchKey = service.take();
for (Entry e : entryList) {
// Call this if the right file is involved
var hans = watchKeyEntryMap.get(watchKey);
if (hans != null) {
for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
// Get the type of the event
WatchEvent.Kind<?> kind = watchEvent.kind();


if (kind == ENTRY_MODIFY) {
Path watchEventPath = (Path) watchEvent.context();
onModified(e.watchFile);
}
if (!watchKey.reset()) {
// Exit if no longer valid
log.debug("Watch key {} was reset", watchKey);
break;
}
}
}
}
}
}
}


@Override
protected Void call() throws Exception {
watchFile();
return null;
}


public abstract void onModified(String watchFile);
}


服务范围:

public abstract class FileWatcherService extends Service<Void> {
    

private final Iterable<String> files;


public FileWatcherService(Iterable<String> files) {
this.files = files;
}


@Override
protected Task<Void> createTask() {
return new FileWatcherTask(files) {
@Override
public void onModified(String watchFile) {
FileWatcherService.this.onModified(watchFile);
}
};
}
    

abstract void onModified(String watchFile);
}