java file nio watchservice

Java 7 WatchService-Ignorar múltiples ocurrencias del mismo evento



file nio (13)

El javadoc para StandardWatchEventKinds.ENTRY_MODIFY dice:

Entrada de directorio modificada. Cuando se registra un directorio para este evento, WatchKey se pone en cola cuando se observa que se ha modificado una entrada en el directorio. El recuento de eventos para este evento es 1 o mayor.

Cuando edite el contenido de un archivo a través de un editor, modificará tanto la fecha (u otros metadatos) como el contenido. Por lo tanto, obtienes dos eventos ENTRY_MODIFY , pero cada uno tendrá un count de 1 (al menos eso es lo que estoy viendo).

Estoy intentando monitorear un archivo de configuración ( servers.cfg previamente registrado con WatchService ) que se actualiza manualmente (es decir, a través de la línea de comando vi ) con el siguiente código:

while(true) { watchKey = watchService.take(); // blocks for (WatchEvent<?> event : watchKey.pollEvents()) { WatchEvent<Path> watchEvent = (WatchEvent<Path>) event; WatchEvent.Kind<Path> kind = watchEvent.kind(); System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind()); // prints (loop on the while twice) // servers.cfg, count: 1, event: ENTRY_MODIFY // servers.cfg, count: 1, event: ENTRY_MODIFY switch(kind.name()) { case "ENTRY_MODIFY": handleModify(watchEvent.context()); // reload configuration class break; case "ENTRY_DELETE": handleDelete(watchEvent.context()); // do something else break; } } watchKey.reset(); }

Como obtiene dos eventos ENTRY_MODIFY , lo anterior recargará la configuración dos veces cuando solo se necesite una vez. ¿Hay alguna manera de ignorar todos menos uno de estos, suponiendo que podría haber más de un evento de ese tipo?

Si la API WatchService tiene tanta utilidad, mucho mejor. (De alguna manera no quiero verificar las horas entre cada evento. Todos los métodos de manejo en mi código son sincrónicos.

Lo mismo ocurre si crea (copia / pega) un archivo de un directorio al directorio vigilado. ¿Cómo puedes combinar ambos en un solo evento?


¿Estás seguro de que hay un problema con jdk7? Me da el resultado correcto (jdk7u15, windows)

Código

import java.io.IOException; import java.nio.file.*; public class WatchTest { public void watchMyFiles() throws IOException, InterruptedException { Path path = Paths.get("c:/temp"); WatchService watchService = path.getFileSystem().newWatchService(); path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); while (true) { WatchKey watchKey = watchService.take(); // blocks for (WatchEvent<?> event : watchKey.pollEvents()) { WatchEvent<Path> watchEvent = (WatchEvent<Path>) event; WatchEvent.Kind<Path> kind = watchEvent.kind(); System.out.println(watchEvent.context() + ", count: " + watchEvent.count() + ", event: " + watchEvent.kind()); // prints (loop on the while twice) // servers.cfg, count: 1, event: ENTRY_MODIFY // servers.cfg, count: 1, event: ENTRY_MODIFY switch (kind.name()) { case "ENTRY_MODIFY": handleModify(watchEvent.context()); // reload configuration class break; case "ENTRY_DELETE": handleDelete(watchEvent.context()); // do something else break; default: System.out.println("Event not expected " + event.kind().name()); } } watchKey.reset(); } } private void handleDelete(Path context) { System.out.println("handleDelete " + context.getFileName()); } private void handleModify(Path context) { System.out.println("handleModify " + context.getFileName()); } public static void main(String[] args) throws IOException, InterruptedException { new WatchTest().watchMyFiles(); } }

La salida es la siguiente: cuando el archivo se copia o se edita usando el bloc de notas.

config.xml, count: 1, event: ENTRY_MODIFY handleModify config.xml

Vi usa muchos archivos adicionales y parece actualizar el atributo de archivo varias veces. notepad ++ hace exactamente dos veces.


Aquí hay una implementación completa que usa timestamps de timestamps para evitar disparar múltiples eventos:

import java.io.File; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import static java.nio.file.StandardWatchEventKinds.*; public abstract class DirectoryWatcher { private WatchService watcher; private Map<WatchKey, Path> keys; private Map<Path, Long> fileTimeStamps; private boolean recursive; private boolean trace = true; @SuppressWarnings("unchecked") private static <T> WatchEvent<T> cast(WatchEvent<?> event) { return (WatchEvent<T>) event; } /** * Register the given directory with the WatchService */ private void register(Path directory) throws IOException { WatchKey watchKey = directory.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE); addFileTimeStamps(directory); if (trace) { Path existingFilePath = keys.get(watchKey); if (existingFilePath == null) { System.out.format("register: %s/n", directory); } else { if (!directory.equals(existingFilePath)) { System.out.format("update: %s -> %s/n", existingFilePath, directory); } } } keys.put(watchKey, directory); } private void addFileTimeStamps(Path directory) { File[] files = directory.toFile().listFiles(); if (files != null) { for (File file : files) { if (file.isFile()) { fileTimeStamps.put(file.toPath(), file.lastModified()); } } } } /** * Register the given directory, and all its sub-directories, with the * WatchService. */ private void registerAll(Path directory) throws IOException { Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs) throws IOException { register(currentDirectory); return FileVisitResult.CONTINUE; } }); } /** * Creates a WatchService and registers the given directory */ DirectoryWatcher(Path directory, boolean recursive) throws IOException { this.watcher = FileSystems.getDefault().newWatchService(); this.keys = new HashMap<>(); fileTimeStamps = new HashMap<>(); this.recursive = recursive; if (recursive) { System.out.format("Scanning %s .../n", directory); registerAll(directory); System.out.println("Done."); } else { register(directory); } // enable trace after initial registration this.trace = true; } /** * Process all events for keys queued to the watcher */ void processEvents() throws InterruptedException, IOException { while (true) { WatchKey key = watcher.take(); Path dir = keys.get(key); if (dir == null) { System.err.println("WatchKey not recognized!!"); continue; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind watchEventKind = event.kind(); // TBD - provide example of how OVERFLOW event is handled if (watchEventKind == OVERFLOW) { continue; } // Context for directory entry event is the file name of entry WatchEvent<Path> watchEvent = cast(event); Path fileName = watchEvent.context(); Path filePath = dir.resolve(fileName); long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath); long newFileModifiedTimeStamp = filePath.toFile().lastModified(); if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp) { fileTimeStamps.remove(filePath); onEventOccurred(); fileTimeStamps.put(filePath, filePath.toFile().lastModified()); } if (recursive && watchEventKind == ENTRY_CREATE) { if (Files.isDirectory(filePath, NOFOLLOW_LINKS)) { registerAll(filePath); } } break; } boolean valid = key.reset(); if (!valid) { keys.remove(key); if (keys.isEmpty()) { break; } } } } public abstract void onEventOccurred(); }

Extienda la clase e implemente el método onEventOccurred() .


Intenté esto y está funcionando perfectamente:

import java.io.IOException; import java.nio.file.*; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static java.nio.file.StandardWatchEventKinds.*; public class FileWatcher implements Runnable, AutoCloseable { private final WatchService service; private final Map<Path, WatchTarget> watchTargets = new HashMap<>(); private final List<FileListener> fileListeners = new CopyOnWriteArrayList<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock r = lock.readLock(); private final Lock w = lock.writeLock(); private final AtomicBoolean running = new AtomicBoolean(false); public FileWatcher() throws IOException { service = FileSystems.getDefault().newWatchService(); } @Override public void run() { if (running.compareAndSet(false, true)) { while (running.get()) { WatchKey key; try { key = service.take(); } catch (Throwable e) { break; } if (key.isValid()) { r.lock(); try { key.pollEvents().stream() .filter(e -> e.kind() != OVERFLOW) .forEach(e -> watchTargets.values().stream() .filter(t -> t.isInterested(e)) .forEach(t -> fireOnEvent(t.path, e.kind()))); } finally { r.unlock(); } if (!key.reset()) { break; } } } running.set(false); } } public boolean registerPath(Path path, boolean updateIfExists, WatchEvent.Kind... eventKinds) { w.lock(); try { WatchTarget target = watchTargets.get(path); if (!updateIfExists && target != null) { return false; } Path parent = path.getParent(); if (parent != null) { if (target == null) { watchTargets.put(path, new WatchTarget(path, eventKinds)); parent.register(service, eventKinds); } else { target.setEventKinds(eventKinds); } return true; } } catch (Throwable e) { e.printStackTrace(); } finally { w.unlock(); } return false; } public void addFileListener(FileListener fileListener) { fileListeners.add(fileListener); } public void removeFileListener(FileListener fileListener) { fileListeners.remove(fileListener); } private void fireOnEvent(Path path, WatchEvent.Kind eventKind) { for (FileListener fileListener : fileListeners) { fileListener.onEvent(path, eventKind); } } public boolean isRunning() { return running.get(); } @Override public void close() throws IOException { running.set(false); w.lock(); try { service.close(); } finally { w.unlock(); } } private final class WatchTarget { private final Path path; private final Path fileName; private final Set<String> eventNames = new HashSet<>(); private final Event lastEvent = new Event(); private WatchTarget(Path path, WatchEvent.Kind[] eventKinds) { this.path = path; this.fileName = path.getFileName(); setEventKinds(eventKinds); } private void setEventKinds(WatchEvent.Kind[] eventKinds) { eventNames.clear(); for (WatchEvent.Kind k : eventKinds) { eventNames.add(k.name()); } } private boolean isInterested(WatchEvent e) { long now = System.currentTimeMillis(); String name = e.kind().name(); if (e.context().equals(fileName) && eventNames.contains(name)) { if (lastEvent.name == null || !lastEvent.name.equals(name) || now - lastEvent.when > 100) { lastEvent.name = name; lastEvent.when = now; return true; } } return false; } @Override public int hashCode() { return path.hashCode(); } @Override public boolean equals(Object obj) { return obj == this || obj != null && obj instanceof WatchTarget && Objects.equals(path, ((WatchTarget) obj).path); } } private final class Event { private String name; private long when; } public static void main(String[] args) throws IOException, InterruptedException { FileWatcher watcher = new FileWatcher(); if (watcher.registerPath(Paths.get("filename"), false, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE)) { watcher.addFileListener((path, eventKind) -> System.out.println(path + " -> " + eventKind.name())); new Thread(watcher).start(); System.in.read(); } watcher.close(); System.exit(0); } }

FileListener:

import java.nio.file.Path; import java.nio.file.WatchEvent; public interface FileListener { void onEvent(Path path, WatchEvent.Kind eventKind); }


No probado, pero quizás esto funcione:

AtomicBoolean modifyEventFired = new AtomicBoolean(); modifyEventFired.set(false); while(true) { watchKey = watchService.take(); // blocks for (WatchEvent<?> event : watchKey.pollEvents()) { WatchEvent<Path> watchEvent = (WatchEvent<Path>) event; WatchEvent.Kind<Path> kind = watchEvent.kind(); System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind()); // prints (loop on the while twice) // servers.cfg, count: 1, event: ENTRY_MODIFY // servers.cfg, count: 1, event: ENTRY_MODIFY switch(kind.name()) { case "ENTRY_MODIFY": if(!modifyEventFired.get()){ handleModify(watchEvent.context()); // reload configuration class modifyEventFired.set(true); } break; case "ENTRY_DELETE": handleDelete(watchEvent.context()); // do something else break; } } modifyEventFired.set(false); watchKey.reset(); }


Resolví este problema al definir una variable booleana global llamada "modifySolver" que es falsa de forma predeterminada. Puede manejar este problema como lo muestro abajo:

else if (eventKind.equals (ENTRY_MODIFY)) { if (event.count() == 2) { getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ()); } /*capture first modify event*/ else if ((event.count() == 1) && (!modifySolver)) { getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ()); modifySolver = true; } /*discard the second modify event*/ else if ((event.count() == 1) && (modifySolver)) { modifySolver = false; } }


Si usa RxJava, puede usar el acelerador del operador al último. En el siguiente ejemplo, solo se emite el último evento en 1000 milisegundos para cada archivo en el directorio supervisado.

public class FileUtils { private static final long EVENT_DELAY = 1000L; public static Observable<FileWatchEvent> watch(Path directory, String glob) { return Observable.<FileWatchEvent>create(subscriber -> { final PathMatcher matcher = directory.getFileSystem().getPathMatcher("glob:" + glob); WatchService watcher = FileSystems.getDefault().newWatchService(); subscriber.setCancellable(watcher::close); try { directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); } catch (IOException e) { subscriber.onError(e); return; } while (!subscriber.isDisposed()) { WatchKey key; try { key = watcher.take(); } catch (InterruptedException e) { if (subscriber.isDisposed()) subscriber.onComplete(); else subscriber.onError(e); return; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind != OVERFLOW) { WatchEvent<Path> ev = (WatchEvent<Path>) event; Path child = directory.resolve(ev.context()); if (matcher.matches(child.getFileName())) subscriber.onNext(new FileWatchEvent(kindToType(kind), child)); } } if (!key.reset()) { subscriber.onError(new IOException("Invalid key")); return; } } }).groupBy(FileWatchEvent::getPath).flatMap(o -> o.throttleLast(EVENT_DELAY, TimeUnit.MILLISECONDS)); } private static FileWatchEvent.Type kindToType(WatchEvent.Kind kind) { if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind)) return FileWatchEvent.Type.ADDED; else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind)) return FileWatchEvent.Type.MODIFIED; else if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind)) return FileWatchEvent.Type.DELETED; throw new RuntimeException("Invalid kind: " + kind); } public static class FileWatchEvent { public enum Type { ADDED, DELETED, MODIFIED } private Type type; private Path path; public FileWatchEvent(Type type, Path path) { this.type = type; this.path = path; } public Type getType() { return type; } public Path getPath() { return path; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FileWatchEvent that = (FileWatchEvent) o; if (type != that.type) return false; return path != null ? path.equals(that.path) : that.path == null; } @Override public int hashCode() { int result = type != null ? type.hashCode() : 0; result = 31 * result + (path != null ? path.hashCode() : 0); return result; } } }


Tuve un problema similar. Sé que es tarde pero podría ayudar a alguien. Solo necesitaba eliminar el duplicado ENTRY_MODIFY . Cada vez que se activa ENTRY_MODIFY, count() devuelve 2 o 1. Si es 1, entonces habrá otro evento con count() 1. Así que simplemente coloque un contador global que guarde el recuento de valores de retorno y realice solo las operaciones cuando el contador se convierte en 2. Algo como esto puede hacer:

WatchEvent event; int count = 0; if(event.count() == 2) count = 2; if(event.count() == 1) count++; if(count == 2){ //your operations here count = 0; }


Tuve un problema similar: estoy usando la API WatchService para mantener los directorios sincronizados, pero observé que, en muchos casos, las actualizaciones se realizaban dos veces. Parece que resolví el problema comprobando la marca de tiempo en los archivos; esto parece descartar la segunda operación de copia. (Al menos en Windows 7 - No puedo estar seguro si funcionará correctamente en otros sistemas operativos)

¿Tal vez podrías usar algo similar? ¿Almacenar la marca de tiempo del archivo y volver a cargar solo cuando se actualice la marca de tiempo?


Una de mis soluciones goto para problemas como este es simplemente poner en cola los recursos de eventos únicos y retrasar el procesamiento durante un tiempo aceptable. En este caso, mantengo un Set<String> que contiene cada nombre de archivo derivado de cada evento que llega. El uso de Set<> garantiza que los duplicados no se agreguen y, por lo tanto, solo se procesarán una vez (por período de demora).

Cada vez que llega un evento interesante, agrego el nombre del archivo al Set<> y reinicio mi temporizador de retardo. Cuando las cosas se calman y el período de retraso transcurre, procedo a procesar los archivos.

Los métodos addFileToProcess () y processFiles () están ''sincronizados'' para garantizar que no se generen ConcurrentModificationExceptions.

Este ejemplo simplificado / independiente es un derivado de WatchDir.java de Oracle:

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; public class DirectoryWatcherService implements Runnable { @SuppressWarnings("unchecked") static <T> WatchEvent<T> cast(WatchEvent<?> event) { return (WatchEvent<T>)event; } /* * Wait this long after an event before processing the files. */ private final int DELAY = 500; /* * Use a SET to prevent duplicates from being added when multiple events on the * same file arrive in quick succession. */ HashSet<String> filesToReload = new HashSet<String>(); /* * Keep a map that will be used to resolve WatchKeys to the parent directory * so that we can resolve the full path to an event file. */ private final Map<WatchKey,Path> keys; Timer processDelayTimer = null; private volatile Thread server; private boolean trace = false; private WatchService watcher = null; public DirectoryWatcherService(Path dir, boolean recursive) throws IOException { this.watcher = FileSystems.getDefault().newWatchService(); this.keys = new HashMap<WatchKey,Path>(); if (recursive) { registerAll(dir); } else { register(dir); } // enable trace after initial registration this.trace = true; } private synchronized void addFileToProcess(String filename) { boolean alreadyAdded = filesToReload.add(filename) == false; System.out.println("Queuing file for processing: " + filename + (alreadyAdded?"(already queued)":"")); if (processDelayTimer != null) { processDelayTimer.cancel(); } processDelayTimer = new Timer(); processDelayTimer.schedule(new TimerTask() { @Override public void run() { processFiles(); } }, DELAY); } private synchronized void processFiles() { /* * Iterate over the set of file to be processed */ for (Iterator<String> it = filesToReload.iterator(); it.hasNext();) { String filename = it.next(); /* * Sometimes you just have to do what you have to do... */ System.out.println("Processing file: " + filename); /* * Remove this file from the set. */ it.remove(); } } /** * Register the given directory with the WatchService */ private void register(Path dir) throws IOException { WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); if (trace) { Path prev = keys.get(key); if (prev == null) { System.out.format("register: %s/n", dir); } else { if (!dir.equals(prev)) { System.out.format("update: %s -> %s/n", prev, dir); } } } keys.put(key, dir); } /** * Register the given directory, and all its sub-directories, with the * WatchService. */ private void registerAll(final Path start) throws IOException { // register directory and sub-directories Files.walkFileTree(start, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (dir.getFileName().toString().startsWith(".")) { return FileVisitResult.SKIP_SUBTREE; } register(dir); return FileVisitResult.CONTINUE; } }); } @SuppressWarnings("unchecked") @Override public void run() { Thread thisThread = Thread.currentThread(); while (server == thisThread) { try { // wait for key to be signaled WatchKey key; try { key = watcher.take(); } catch (InterruptedException x) { return; } Path dir = keys.get(key); if (dir == null) { continue; } for (WatchEvent<?> event: key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind == OVERFLOW) { continue; } if (kind == ENTRY_MODIFY) { WatchEvent<Path> ev = (WatchEvent<Path>)event; Path name = ev.context(); Path child = dir.resolve(name); String filename = child.toAbsolutePath().toString(); addFileToProcess(filename); } } key.reset(); } catch (Exception e) { e.printStackTrace(); } } } public void start() { server = new Thread(this); server.setName("Directory Watcher Service"); server.start(); } public void stop() { Thread moribund = server; server = null; if (moribund != null) { moribund.interrupt(); } } public static void main(String[] args) { if (args==null || args.length == 0) { System.err.println("You need to provide a path to watch!"); System.exit(-1); } Path p = Paths.get(args[0]); if (!Files.isDirectory(p)) { System.err.println(p + " is not a directory!"); System.exit(-1); } DirectoryWatcherService watcherService; try { watcherService = new DirectoryWatcherService(p, true); watcherService.start(); } catch (IOException e) { System.err.println(e.getMessage()); } } }


WatcherServices informa eventos dos veces porque el archivo subyacente se actualiza dos veces. Una vez para el contenido y una vez para la hora modificada del archivo. Estos eventos ocurren en un corto espacio de tiempo. Para resolver esto, duerma entre las llamadas poll() o take() y la llamada key.pollEvents() . Por ejemplo:

@Override @SuppressWarnings( "SleepWhileInLoop" ) public void run() { setListening( true ); while( isListening() ) { try { final WatchKey key = getWatchService().take(); final Path path = get( key ); // Prevent receiving two separate ENTRY_MODIFY events: file modified // and timestamp updated. Instead, receive one ENTRY_MODIFY event // with two counts. Thread.sleep( 50 ); for( final WatchEvent<?> event : key.pollEvents() ) { final Path changed = path.resolve( (Path)event.context() ); if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) { System.out.println( "Changed: " + changed ); } } if( !key.reset() ) { ignore( path ); } } catch( IOException | InterruptedException ex ) { // Stop eavesdropping. setListening( false ); } } }

Llamar a sleep() ayuda a eliminar las llamadas dobles. La demora puede tener que ser tan alta como tres segundos.


WatchDir.java la WatchDir.java de WatchDir.java y @ nilesh de Oracle en una clase Observable que notificará a sus observadores una vez cuando se modifique el archivo visto.

Traté de hacerlo tan legible y corto como sea posible, pero aún así aterrizó con más de 100 líneas. Mejoras bienvenidas, por supuesto.

Uso:

FileChangeNotifier fileReloader = new FileChangeNotifier(File file); fileReloader.addObserver((Observable obj, Object arg) -> { System.out.println("File changed for the " + arg + " time."); });

Ver mi solución en GitHub: FileChangeNotifier.java .


WatchDir.java para recibir solo modificaciones hechas por humanos. Comparando .lastModified() de un archivo.

long lastModi=0; //above for loop if(kind==ENTRY_CREATE){ System.out.format("%s: %s/n", event.kind().name(), child); }else if(kind==ENTRY_MODIFY){ if(child.toFile().lastModified() - lastModi > 1000){ System.out.format("%s: %s/n", event.kind().name(), child); } }else if(kind==ENTRY_DELETE){ System.out.format("%s: %s/n", event.kind().name(), child); } lastModi=child.toFile().lastModified();


/** * * * in windows os, multiple event will be fired for a file create action * this method will combine the event on same file * * for example: * * pathA -> createEvent -> createEvent * pathA -> createEvent + modifyEvent, .... -> modifyEvent * pathA -> createEvent + modifyEvent, ...., deleteEvent -> deleteEvent * * * * 在windows环境下创建一个文件会产生1个创建事件+多个修改事件, 这个方法用于合并重复事件 * 合并优先级为 删除 > 更新 > 创建 * * * @param events * @return */ private List<WatchEvent<?>> filterEvent(List<WatchEvent<?>> events) { // sorted by event create > modify > delete Comparator<WatchEvent<?>> eventComparator = (eventA, eventB) -> { HashMap<WatchEvent.Kind, Integer> map = new HashMap<>(); map.put(StandardWatchEventKinds.ENTRY_CREATE, 0); map.put(StandardWatchEventKinds.ENTRY_MODIFY, 1); map.put(StandardWatchEventKinds.ENTRY_DELETE, 2); return map.get(eventA.kind()) - map.get(eventB.kind()); }; events.sort(eventComparator); HashMap<String, WatchEvent<?>> hashMap = new HashMap<>(); for (WatchEvent<?> event : events) { // if this is multiple event on same path // the create event will added first // then override by modify event // then override by delete event hashMap.put(event.context().toString(), event); } return new ArrayList<>(hashMap.values()); }