我尝试查看某些文件的更改。
但是我从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效果:
关于java - WatchKey始终为null,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54749397/