Когда я пытаюсь зарегистрировать файл вместо каталога java.nio.file.NotDirectoryException
. Могу ли я слушать одно изменение файла, а не весь каталог?
Могу ли я наблюдать за сменой одного файла с помощью WatchService (а не всего каталога)?
Ответ 1
Просто отфильтруйте события для файла, который вы хотите в каталоге:
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", если он тогда делает что угодно.
Ответ 2
Нет, невозможно зарегистрировать файл, служба часов не работает таким образом. Но регистрация каталога на самом деле отслеживает изменения в дочерних папках каталога (файлы и подкаталоги), а не изменения в самой директории.
Если вы хотите посмотреть файл, то вы регистрируете каталог, содержащий службу часов. Документация Path.register() говорит:
WatchKey java.nio.file.Path.register(WatchService watcher, Kind [] events, Modifier... модификаторы) бросает IOException
Регистрирует файл, расположенный по этому пути, с помощью службы часов.
В этом выпуске этот путь определяет каталог, который существует. Каталог зарегистрирован с помощью службы просмотра, чтобы можно было просматривать записи в каталоге
Затем вам нужно обработать события в записях и определить те, которые связаны с интересующим вас файлом, путем проверки контекстного значения события. Значение контекста представляет собой имя записи (фактически путь к записи относительно пути ее родителя, который является именно дочерним именем). Здесь приведен пример .
Ответ 3
Другие ответы правильные, что вы должны смотреть каталог и фильтровать свой файл. Однако вы, вероятно, хотите, чтобы поток работал в фоновом режиме. Принимаемый ответ может неограниченно блокироваться на 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()
в потоке.
Ответ 4
Apache предлагает FileWatchDog класс с помощью метода 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()
. Собственный WatchService от Java NIO более эффективен, так как уведомления являются немедленными.
Ответ 5
Вы не можете смотреть отдельный файл напрямую, но вы можете отфильтровать то, что вам не нужно.
Вот моя реализация класса 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();
Ответ 6
Не уверен в отношении других, но я стонаю за количество кода, необходимого для просмотра одного файла для изменений с использованием базового API WatchService. Это должно быть проще!
Вот несколько альтернатив с использованием сторонних библиотек:
- Использование конфигурации Apache Commons
- Используя spring -груженный пакет из Spring Framework (не нашел пример реализации для этого вне поля, но это выглядит прямолинейно)
Ответ 7
Я создал оболочку вокруг 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);
}
Полный код находится в этом Gist.