Kann ich mit WatchService auf Änderungen einzelner Dateien achten (nicht auf das gesamte Verzeichnis)?

74

Wenn ich versuche, eine Datei anstelle eines Verzeichnisses zu registrieren, java.nio.file.NotDirectoryExceptionwird ausgelöst. Kann ich auf eine einzelne Dateiänderung warten, nicht auf das gesamte Verzeichnis?

fedor.belov
quelle
7
Javadoc: "In dieser Version findet dieser Pfad ein vorhandenes Verzeichnis." ==> Die Antwort lautet also "Nein, Sie können keine Datei registrieren". Dann: "Das Verzeichnis wird beim Überwachungsdienst registriert, damit Einträge im Verzeichnis überwacht werden können." ==> Wenn Sie also ein Verzeichnis registrieren, suchen Sie tatsächlich nach Ereignissen in Verzeichniseinträgen, nicht im Verzeichnis selbst. Der Name der Ereignisse erinnert an ihre Beziehung, sie beginnen mit ENTRY_, wie "ENTRY_MODIFY - Eintrag im Verzeichnis wurde geändert". Die ausgewählte Antwort enthält die Details zur Verwendung des Ereignisses.
Minuten

Antworten:

100

Filtern Sie einfach die Ereignisse für die gewünschte Datei im Verzeichnis:

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

Hier prüfen wir, ob die geänderte Datei "myFile.txt" ist, wenn es dann was auch immer ist.

Boris die Spinne
quelle
3
+1, das ist toll, danke! Gibt es eine andere effiziente Möglichkeit, eine einzelne Datei anzusehen?
ADJ
3
Vergessen Sie nicht zu testen, ob das Ereignis ist OVERFLOW. Sie müssen sich dafür nicht registrieren. Beispiel hier .
Venkata Raju
3
Die unausgesprochene Falle hierbei ist, dass die Implementierungen des Watchers auf einigen Plattformen das Verzeichnis sperren können, in das Sie den Watcher gestellt haben.
Trejkaz
8
final WatchKey watchKey - wofür ist das? Diese Variable scheint später nicht mehr verwendet zu werden.
MVMN
1
Stellen Sie sicher, dass Sie nicht versuchen, nach einer Datei in einem Ressourcenverzeichnis zu suchen. Es fühlt sich an, als würde es funktionieren, aber nicht, weil sich die Datei ändert, aber die Ressource wird nicht automatisch neu geladen.
Nick
21

Nein, es ist nicht möglich, eine Datei zu registrieren. Der Überwachungsdienst funktioniert auf diese Weise nicht. Beim Registrieren eines Verzeichnisses werden jedoch Änderungen an den untergeordneten Verzeichnissen (den Dateien und Unterverzeichnissen) und nicht an den Änderungen am Verzeichnis selbst überwacht.

Wenn Sie eine Datei ansehen möchten, registrieren Sie das enthaltene Verzeichnis beim Überwachungsdienst. Die Dokumentation zu Path.register () lautet:

WatchKey java.nio.file.Path.register (WatchService-Beobachter, Kind [] -Ereignisse, Modifikator ... Modifikatoren) löst eine IOException aus

Registriert die unter diesem Pfad befindliche Datei bei einem Überwachungsdienst.

In dieser Version sucht dieser Pfad ein vorhandenes Verzeichnis. Das Verzeichnis ist beim Überwachungsdienst registriert , damit Einträge im Verzeichnis überwacht werden können

Anschließend müssen Sie Ereignisse für Einträge verarbeiten und diejenigen identifizieren, die sich auf die Datei beziehen, an der Sie interessiert sind, indem Sie den Kontextwert des Ereignisses überprüfen. Der Kontextwert stellt den Namen des Eintrags dar (tatsächlich den Pfad des Eintrags relativ zum Pfad seines übergeordneten Eintrags, der genau der untergeordnete Name ist). Sie haben hier ein Beispiel .

min
quelle
19

Andere Antworten sind richtig, dass Sie ein Verzeichnis überwachen und nach Ihrer bestimmten Datei filtern müssen. Sie möchten jedoch wahrscheinlich, dass ein Thread im Hintergrund ausgeführt wird. Die akzeptierte Antwort kann auf unbestimmte Zeit blockiert werden watchService.take();und schließt den WatchService nicht. Eine Lösung, die für einen separaten Thread geeignet ist, könnte folgendermaßen aussehen:

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

Ich habe versucht, anhand der akzeptierten Antwort und dieses Artikels zu arbeiten . Sie sollten in der Lage sein, diesen Thread mit zu verwenden new FileWatcher(new File("/home/me/myfile")).start()und ihn zu stoppen, indem Sie stopThread()den Thread aufrufen .

timrs2998
quelle
1
Denken Sie daran, dass Nicht-Daemon-Threads das Beenden der JVM verhindern. Rufen Sie daher setDaemon(boolean)vorher auf, run()je nachdem, wie sich Ihre App verhalten soll.
Chris H.
10

Apache bietet eine FileWatchDog- Klasse mit einer doOnChangeMethode an.

private class SomeWatchFile extends FileWatchdog {

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

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

}

Und wo immer Sie wollen, können Sie diesen Thread starten:

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

Die FileWatchDog-Klasse fragt den lastModified()Zeitstempel einer Datei ab . Der native WatchService von Java NIO ist effizienter, da Benachrichtigungen sofort erfolgen.

Ich Hund
quelle
9
Wäre schön zu wissen, aus welcher Bibliothek die FileWatchdog-Klasse stammt?
Szydan
1
von log4j - org.apache.log4j.helpers
idog
8

Sie können eine einzelne Datei nicht direkt ansehen, aber Sie können herausfiltern, was Sie nicht benötigen.

Hier ist meine FileWatcherKlassenimplementierung:

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

Um dies zu verwenden, müssen Sie die onModified()Methode nur folgendermaßen erweitern und implementieren :

import java.io.File;

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

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

Beginnen Sie schließlich mit dem Betrachten der Datei:

String watchFile = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Test.txt";
FileWatcher fileWatcher = new MyFileWatcher(watchFile);
fileWatcher.watchFile();
BullyWiiPlaza
quelle
sauberer Arbeitscode! erklärende und gut geschriebene Antwort!
Minhas Kamal
5

Ich bin mir bei anderen nicht sicher, aber ich stöhne über die Menge an Code, die erforderlich ist, um eine einzelne Datei mithilfe der grundlegenden WatchService-API auf Änderungen zu überwachen. Es muss einfacher sein!

Hier sind einige Alternativen, die Bibliotheken von Drittanbietern verwenden:

John Rix
quelle
5

Ich habe einen Wrapper um Java 1.7 erstellt WatchService, mit dem ein Verzeichnis und eine beliebige Anzahl von Glob- Mustern registriert werden können. Diese Klasse kümmert sich um die Filterung und gibt nur Ereignisse aus, an denen Sie interessiert sind.

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

Der vollständige Code befindet sich in diesem Repo .

Hindol
quelle