Я бы хотел добавить к этому ответу префикс, сказав, что 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
.
Пытаясь решить проблему невидения последующих событий, я наивно добавил вызовдо reset()
, ничего не делая.Это привело к тому, что подавляющее количество B
s печаталось на консоли.Я был сбит с толку, потому что я только один раз изменил файл, но затем я прочитал документацию WatchKey.reset
( выделение мое):
Сброс этого ключа часов.
Если этот ключ отслеживания был отменен или этот ключ отслеживания уже находится в состоянии готовности, то вызов этого метода не имеет никакого эффекта. В противном случае, если для объекта есть ожидающие события, тогда эта клавиша наблюдения немедленно помещается в очередь для службы наблюдения. Если нет ожидающих событий, тогда клавиша наблюдения переводится в состояние готовности и остается в этом состоянии.состояние, пока не будет обнаружено событие или пока кнопка отмены не будет отменена.
То, что я видел, было просто одним и тем же событием снова и снова, потому что я никогда не обрабатывал его.После добавления звонка к WatchEvent.pollEvents()
я больше не спамился с B
s.
Вы создаете новый
WatchService
для каждого файла, который хотите просмотреть.
Похоже, вам нужен класс, который может просматривать произвольное количество файлов (и только эти файлы).Это не требует WatchService
на файл, так как вы можете зарегистрировать несколько каталогов с одним WatchService
.В случае, если вам потребуется , требуется , чтобы использовать несколько WatchService
с, если файлы поступят из разных FileSystem
с.Однако в вашем коде последовательно используется файловая система по умолчанию .
* 1.092 * Использование того же
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-файл в действии: