44

javadocStandardWatchEventKinds.ENTRY_MODIFY说:

目录条目已修改。当为此事件注册目录时,当观察到目录中的条目已被修改时,WatchKey 将排队。此事件的事件计数为 1 或更大。

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

我正在尝试使用以下代码监视手动更新(即通过命令行)的配置文件(servers.cfg以前注册过) :WatchServicevi

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事件,因此仅需要一次时,上述内容将重新加载配置两次。假设可能有不止一个这样的事件,是否有任何方法可以忽略除其中之一之外的所有事件?

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

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

4

15 回答 15

53

WatcherServices 报告事件两次,因为基础文件更新了两次。一次用于内容,一次用于文件修改时间。这些事件发生在很短的时间内。为了解决这个问题,在poll()ortake()调用和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()有助于消除双重呼叫。延迟可能必须高达三秒。

于 2014-08-09T18:10:31.010 回答
16

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

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

于 2013-06-04T11:03:29.650 回答
6

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

每次发生有趣的事件时,我都会将文件名添加到Set<>并重新启动延迟计时器。当事情稳定下来并且延迟期过去时,我会继续处理文件。

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

这个简化/独立的示例是 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: %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());
        }
    }

}
于 2016-01-15T19:41:15.883 回答
4

我修改了WatchDir.java以仅接收人为的修改。比较.lastModified()一个文件。

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();
于 2015-04-04T04:32:10.483 回答
3

这是一个完整的实现,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: %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();
}

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

于 2016-11-16T20:56:34.140 回答
2

你确定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++ 正好做了两次。

于 2013-06-11T05:47:46.287 回答
2

如果你使用 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;
        }
    }
}
于 2017-07-22T09:11:11.567 回答
1

我通过定义一个名为“modifySolver”的全局布尔变量解决了这个问题,默认情况下它是假的。你可以处理这个问题,如下所示:

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;
            }
        }
于 2016-05-10T05:51:16.367 回答
1

我将 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 。

于 2015-07-31T11:59:49.080 回答
0

我试过了,它工作得很好:

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

}

文件监听器:

import java.nio.file.Path;
import java.nio.file.WatchEvent;

public interface FileListener {

    void onEvent(Path path, WatchEvent.Kind eventKind);

}
于 2015-05-31T11:49:12.190 回答
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());


}
于 2018-03-27T08:33:53.593 回答
0

如果您在使用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
    }
  }

}
于 2019-05-09T06:19:23.833 回答
0

我有类似的问题。我知道这已经晚了,但它可能会对某人有所帮助。我只需要消除重复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;
}
于 2015-11-05T14:17:15.950 回答
0

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

1.用vim新建文件

MODIFIED, CREATED 事件被触发

2.用Vim修改文件

DELETED, MODIFIED, CREATED 事件被触发

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

MODIFIED 事件被触发

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

MODIFIED, CREATED 如果不存在同名文件则触发 CREATED 如果同名文件存在则触发

我使用 3 个地图在 for 循环中迭代 WatchEvent 中收集 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);
        }
于 2021-08-14T07:55:18.737 回答
-1

未经测试,但也许这会起作用:

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();       
}
于 2013-05-27T20:37:31.663 回答