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

Posted

技术标签:

【中文标题】WatchService 有时会触发 ENTRY_MODIFY 两次,有时会触发一次【英文标题】:WatchService fires ENTRY_MODIFY sometimes twice and sometimes once 【发布时间】:2017-01-02 00:44:16 【问题描述】:

我正在使用来自 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 事件。

我在*** 中找到了this。这个问题问为什么两个ENTRY_MODIFY 被解雇了。这不完全是我的问题,但它的答案之一与我的要求存在争议。可悲的是,在这场争论中我的问题没有解决方案。

因为最后没有触发ENTRY_MODIFY,而只是在复制开始时,所以我无法理解复制何时完成。您认为这可能是什么原因?可以修复吗,我怎么理解复制完成了?我不能改变rhel 7.2,但除此之外我很乐意接受。提前致谢。

【问题讨论】:

您好,请查看我的回答here,以了解您的文件何时被复制。 HTH。 感谢您的回复,您的评论我看了很多遍。但是,我不明白获取文件锁将如何帮助我了解复制过程何时结束。能详细解释一下吗? 只要复制过程忙,文件就会保持锁定以供其他进程/线程进行 RW 访问。我的答案中的代码片段试图获得对该文件的锁定(在您通过ENTRY_CREATED 事件知道它的存在之后)。一旦文件不再被复制过程锁定,将立即授予锁定。至此,复制过程完成。 您好,在 processEvents 方法中我检查 ENTRY_CREATE 事件,然后将您的代码块添加到该条件中。但是,它会触发 java.io.FileNotFoundExcepiton :该进程无法访问该文件,因为它正在被另一个进程使用。也许我用错了。您能否在我的示例中添加您的代码块,看看它是否有效。这样我才能接受你的回答。 【参考方案1】:

您可以使用DelayQueue 对事件进行重复数据删除。

【讨论】:

【参考方案2】:

我只是检查文件长度是否为零。 这里是一个例子

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

【讨论】:

很高兴在 *** 上找到一个天才 - 我没有运气在另一个线程上找到我喜欢的任何东西,但这很棒if (path.toFile().length() &gt; 0)

以上是关于WatchService 有时会触发 ENTRY_MODIFY 两次,有时会触发一次的主要内容,如果未能解决你的问题,请参考以下文章

java如何实现linux下实时监控文件是不是有变化

Java 文件系统监控(WatchService)

WatchService 可能存在不能再window工作

java 使用Java WatchService观察目录

UICollectionViewReusableView 错误:选择一个标题有时会触发另一个

Java 7 WatchService 对其他人来说很慢吗?