Java 7 WatchService -忽略同一事件的多次出现



StandardWatchEventKinds.ENTRY_MODIFY的javadoc表示:

目录条目已修改。当一个目录为此注册时事件时,当观察到表中有条目时,将WatchKey排队已完成目录的修改。此事件的事件计数为1或更高。

当您通过编辑器编辑文件内容时,它将修改日期(或其他元数据)和内容。因此,您将获得两个ENTRY_MODIFY事件,但每个事件的count都为1(至少这是我所看到的)。

我正试图监视手动更新的配置文件(servers.cfg以前与WatchService注册)。通过命令行vi),使用以下代码:

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

由于您获得了两个ENTRY_MODIFY事件,因此上面的代码将在只需要一次时重新加载配置两次。假设可能有不止一个这样的事件,有没有办法忽略除一个之外的所有事件?

如果WatchService API有这样的实用程序,那就更好了。(我有点不想检查每个事件之间的时间。我代码中的所有处理程序方法都是同步的。

如果您从一个目录创建(复制/粘贴)一个文件到监视的目录,也会发生同样的事情。你如何将这两者结合成一个事件呢?

WatcherServices报告两次事件,因为底层文件更新了两次。一次用于内容,一次用于文件修改时间。这些事件在很短的时间内发生。为了解决这个问题,在poll()take()调用和key.pollEvents()调用之间休眠。例如:

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

调用sleep()有助于消除重复调用。延迟可能会高达三秒。

我有一个类似的问题-我使用WatchService API来保持目录同步,但观察到在许多情况下,更新被执行了两次。我似乎通过检查文件上的时间戳解决了这个问题-这似乎屏蔽了第二次复制操作。(至少在windows 7中-我不能确定它是否能在其他操作系统中正常工作)

也许你可以使用类似的东西?存储文件中的时间戳并仅在时间戳更新时重新加载?

对于此类问题,我的解决方案之一是简单地将唯一事件资源排队,并将处理延迟一段可接受的时间。在本例中,我维护一个Set<String>,它包含从每个到达的事件派生的每个文件名。使用Set<>确保不会添加重复项,因此只会处理一次(每个延迟周期)。

每当一个有趣的事件到达时,我将文件名添加到Set<>并重新启动延迟计时器。当事情解决了,延迟时间过去了,我就开始处理文件。

addFileToProcess()和processFiles()方法是"同步的",以确保不会抛出concurrentmodificationexception。

这个简化的/独立的例子是Oracle的WatchDir.java:

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: %sn", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %sn", 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());
        }
    }
}

我修改了WatchDir.java,只接收人为修改。比较文件的.lastModified()

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

下面是使用timestamps来避免触发多个事件的完整实现:

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: %sn", directory);
            } else
            {
                if (!directory.equals(existingFilePath))
                {
                    System.out.format("update: %s -> %sn", 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();
}

扩展类并实现onEventOccurred()方法。

确定jdk7有问题吗?它给我正确的结果(jdk7u15, windows)

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

输出如下-当文件被复制或使用记事本编辑时。

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

Vi使用了许多额外的文件,并且似乎多次更新文件属性。notepad++只做了两次。

如果您使用RxJava,您可以使用操作符throttleLast。在下面的示例中,只对监视目录中的每个文件发出1000毫秒内的最后一个事件。

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

我试过了,效果很好:

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

我通过定义一个名为"modifySolver"的全局布尔变量来解决这个问题,该变量默认为false。您可以按照如下所示处理此问题:

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

我将Oracle的WatchDir.java和@nilesh的建议编译成一个Observable类,当被监视的文件被更改时,它将通知它的观察者一次。

我试着让它尽可能简明易懂,但最终还是写了100多行。当然欢迎改进。

用法:

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

查看我在GitHub上的解决方案:FileChangeNotifier.java.

我也遇到过类似的问题。我知道有点晚了,但也许能帮到别人。我只需要消除重复的ENTRY_MODIFY。当ENTRY_MODIFY被触发时,count()返回2或1。如果它是1,那么将有另一个事件与count() 1。所以只要放一个全局计数器,它保持返回值的计数,并只在计数器变为2时执行操作。可以这样做:

WatchEvent event; 
int count = 0;
if(event.count() == 2)
     count = 2;
if(event.count() == 1)
     count++;
if(count == 2){
     //your operations here
     count = 0;
}
    /**
 * 
 * 
 * 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());

}

如果您正在使用better-files-akka库尝试在Scala中使用相同的方法,我已经根据已接受的答案中提出的解决方案完成了这项工作。

https://github.com/pathikrit/better-files/issues/313

trait ConfWatcher {
  implicit def actorSystem: ActorSystem
  private val confPath = "/home/codingkapoor/application.conf"
  private val appConfFile = File(confPath)
  private var appConfLastModified = appConfFile.lastModifiedTime
  val watcher: ActorRef = appConfFile.newWatcher(recursive = false)
  watcher ! on(EventType.ENTRY_MODIFY) { file =>
    if (appConfLastModified.compareTo(file.lastModifiedTime) < 0) {
      // TODO
      appConfLastModified = file.lastModifiedTime
    }
  }
}

多个事件的集合取决于用于创建/修改文件的工具。

1。用Vim

创建新文件

修改,创建事件被触发

2。使用Vim

修改文件

删除,修改,创建事件被触发

3。使用Linux命令'mv'将文件从其他文件夹移动到监视文件夹

修改事件被触发

4。使用Linux命令'cp'将文件从其他文件夹复制到监视文件夹

MODIFIED, CREATED如果不存在相同文件名的文件将被触发

如果存在相同文件名的文件,则触发create。

我使用3个映射来收集在WatchEvent上迭代的for循环中的CREATED/MODIFIED/DELETED条目。然后,在这3个映射上运行3个for循环,以确定我们需要通知的正确事件(例如:如果一个文件名出现在所有三个映射中,那么我们可以说它是一个MODIFIED事件)

            File f = new File(sourceDir);
        if (!f.exists() || !f.isDirectory()) {
            LOGGER.warn("File " + sourceDir + " does not exist OR is not a directory");
            return;
        }
        WatchService watchService = FileSystems.getDefault().newWatchService();
        Path watchedDir = f.toPath();
        watchedDir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        while (true) {
            try {
                WatchKey watchKey = watchService.take();
                Thread.sleep(checkInterval);
                //Events fired by Java NIO file watcher depends on the tool used
                //to create/update/delete file. We need those 3 maps to collect
                //all entries fired by NIO watcher. Then, make 3 loops at the end
                //to determine the correct unique event to notify
                final Map<String, Boolean> createdEntries = new HashMap<>();
                final Map<String, Boolean> modifiedEntries = new HashMap<>();
                final Map<String, Boolean> deletedEntries = new HashMap<>();
                List<WatchEvent<?>> events = watchKey.pollEvents();
                for (WatchEvent<?> event : events) {
                    if (event.kind() == OVERFLOW) {
                        continue;
                    }
                    WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
                    WatchEvent.Kind<Path> kind = pathEvent.kind();
                    Path path = pathEvent.context();
                    String fileName = path.toString();
                    if (accept(fileName)) {
                        if (kind == ENTRY_CREATE) {
                            createdEntries.put(fileName, true);
                        } else if (kind == ENTRY_MODIFY) {
                            modifiedEntries.put(fileName, true);
                        } else if (kind == ENTRY_DELETE) {
                            deletedEntries.put(fileName, true);
                        }
                    }
                }
                long timeStamp = System.currentTimeMillis();
                final Map<String, Boolean> handledEntries = new HashMap<>();
                //3 for loops to determine correct event to notify
                for (String key : createdEntries.keySet()) {
                    if (handledEntries.get(key) == null) {
                        Boolean modified = modifiedEntries.get(key);
                        Boolean deleted = deletedEntries.get(key);
                        if (modified != null && deleted != null) {
                            //A triplet of DELETED/MODIFIED/CREATED means a MODIFIED event
                            LOGGER.debug("File " + key + " was modified");
                            notifyFileModified(key, timeStamp);
                        } else if (modified != null) {
                            LOGGER.debug("New file " + key + " was created");
                            notifyFileCreated(key, timeStamp);
                        } else {
                            LOGGER.debug("New file " + key + " was created");
                            notifyFileCreated(key, timeStamp);
                        }
                        handledEntries.put(key, true);
                    }
                }
                for (String key : modifiedEntries.keySet()) {
                    if (handledEntries.get(key) == null) {
                        //Current entry survives from loop on CREATED entries. It is certain
                        //that we have MODIFIED event
                        LOGGER.debug("File " + key + " was modified");
                        notifyFileModified(key, timeStamp);
                        handledEntries.put(key, true);
                    }
                }
                for (String key : deletedEntries.keySet()) {
                    if (handledEntries.get(key) == null) {
                        //Current entry survives from two loops on CREATED/MODIFIED entries. It is certain
                        //that we have DELETE event
                        LOGGER.debug("File " + key + " was deleted");
                        notifyFileDeleted(key, timeStamp);
                    }
                }
                boolean valid = watchKey.reset();
                if (!valid) {
                    break;
                }
            } catch (Exception ex) {
                LOGGER.warn("Error while handling file events under: " + sourceDir, ex);
            }
            Thread.sleep(checkInterval);
        }

未经测试,但也许这将工作:

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

相关内容

  • 没有找到相关文章

最新更新