WatchService 有时会触发两次,有时只触发一次 ENTRY_MODIFY。

9

我正在使用来自Oracle的WatchService示例:

import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

public class WatchDir {

private final WatchService watcher;
private final Map<WatchKey,Path> keys;
private final boolean recursive;
private boolean trace = false;

@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
    return (WatchEvent<T>)event;
}

/**
 * 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
        {
            register(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}

/**
 * Creates a WatchService and registers the given directory
 */
WatchDir(Path dir, boolean recursive) throws IOException {
    this.watcher = FileSystems.getDefault().newWatchService();
    this.keys = new HashMap<WatchKey,Path>();
    this.recursive = recursive;

    if (recursive) {
        System.out.format("Scanning %s ...\n", dir);
        registerAll(dir);
        System.out.println("Done.");
    } else {
        register(dir);
    }

    // enable trace after initial registration
    this.trace = true;
}

/**
 * Process all events for keys queued to the watcher
 */
void processEvents() {
    for (;;) {

        // wait for key to be signalled
        WatchKey key;
        try {
            key = watcher.take();
        } catch (InterruptedException x) {
            return;
        }

        Path dir = keys.get(key);
        if (dir == null) {
            System.err.println("WatchKey not recognized!!");
            continue;
        }

        for (WatchEvent<?> event: key.pollEvents()) {
            WatchEvent.Kind kind = event.kind();

            // TBD - provide example of how OVERFLOW event is handled
            if (kind == OVERFLOW) {
                continue;
            }

            // Context for directory entry event is the file name of entry
            WatchEvent<Path> ev = cast(event);
            Path name = ev.context();
            Path child = dir.resolve(name);

            // print out event
            System.out.format("%s: %s\n", event.kind().name(), child);

            // if directory is created, and watching recursively, then
            // register it and its sub-directories
            if (recursive && (kind == ENTRY_CREATE)) {
                try {
                    if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
                        registerAll(child);
                    }
                } catch (IOException x) {
                    // ignore to keep sample readbale
                }
            }
        }

        // reset key and remove from set if directory no longer accessible
        boolean valid = key.reset();
        if (!valid) {
            keys.remove(key);

            // all directories are inaccessible
            if (keys.isEmpty()) {
                break;
            }
        }
    }
}

static void usage() {
    System.err.println("usage: java WatchDir [-r] dir");
    System.exit(-1);
}

public static void main(String[] args) throws IOException {
    // parse arguments
    if (args.length == 0 || args.length > 2)
        usage();
    boolean recursive = false;
    int dirArg = 0;
    if (args[0].equals("-r")) {
        if (args.length < 2)
            usage();
        recursive = true;
        dirArg++;
    }

    // register directory and process its events
    Path dir = Paths.get(args[dirArg]);
    new WatchDir(dir, recursive).processEvents();
}
}

我正在 Windows 7 中开发一个应用程序,部署环境是 rhel 7.2。在两个操作系统中,每当我复制一个文件时,它都会触发一个 ENTRY_CREATED 事件,然后触发两个 ENTRY_MODIFY 事件。第一个 ENTRY_MODIFY 出现在开始复制文件时,第二个 ENTRY_MODIFY 出现在复制结束时,因此我能够理解复制过程何时结束。但是,现在在 rhel 7.2 中只会触发一个 ENTRY_MODIFY 事件。但是在 Windows 7 中仍然会触发两个 ENTRY_MODIFY 事件。
我在 stackoverflow 上找到了这个问题。该问题问为什么会出现两个 ENTRY_MODIFY 事件,虽然不完全是我的问题,但其中一些答案驳斥了我的问题。可惜,在那场争论中没有解决我的问题。
由于在复制结束时没有触发 ENTRY_MODIFY 事件,我无法确定复制何时结束。你认为这可能是什么原因?这可以修复吗?我能如何确定复制已经结束?除了 rhel 7.2,我都可以接受任何其他解决方案。谢谢!

你好,请看一下我在这里的回答(https://dev59.com/HJLea4cB1Zd3GeqP686C#34718685),其中介绍了一种知道文件何时被复制的方法。希望对你有所帮助。 - TT.
谢谢您的回复,我已经多次阅读了您的评论。然而,我不明白如何通过获取文件锁来帮助我理解何时复制过程结束。您能否请详细解释一下? - halil
只要复制过程仍在进行中,文件将保持被其他进程/线程锁定以进行读写访问。我回答中的代码片段尝试在发现该文件(通过ENTRY_CREATED事件)后对其进行锁定。一旦该文件不再被复制进程锁定,锁定将被授予。此时,复制过程已完成。 - TT.
你好,我在 processEvents 方法中检查 ENTRY_CREATE 事件,然后将你的代码块添加到该条件中。但是,它会触发 java.io.FileNotFoundException 异常:由于另一个进程正在使用该文件,因此无法访问该文件。也许我使用方法不正确。你能否在我的示例中添加你的代码块并查看是否有效,这样我就可以接受你的答案了。 - halil
2个回答

3

我只是检查文件长度是否为零。

以下是一个示例:

for (WatchEvent<?> event : key.pollEvents()) {
    Path fileName = (Path) event.context();
    if("tomcat-users.xml".equals(fileName.toString())) {
        Path tomcatUsersXml = tomcatConf.resolve(fileName);
        if(tomcatUsersXml.toFile().length() > 0) {
            load(tomcatUsersXml);
        }
    } 
}

很高兴在stackoverflow上找到一个天才 - 在其他线程上我没有找到任何我喜欢的东西,但这很棒 if (path.toFile().length() > 0) - ycomp

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接