WatchService 轮询不正确

WatchService not polling correctly

我想每 10 秒轮询一次目录,看看是否有任何文件被添加或修改。如果它们在 10 秒内有任何变化,我想要一组所有文件路径,然后我可以将其传递给另一个方法。

问题

添加文件后,它会立即被识别并调用 addedFiles 方法。相反,我希望它等待 10 秒,然后使用已找到的多个文件调用 addedFiles 方法。

例子
我创建了一个监视目录的完整示例。然后一个线程等待 5 秒并将 2000 个文件复制到监视目录中。
WatchService 的预期行为是每 10 秒检查一次更改。相反,它似乎正在立即获取更改。

代码

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

public class DirectoryWatcherExample 
{
    private static final int POLLING_TIME = 10;

    public static void main(final String args[]) throws InterruptedException, IOException
    {
        final Path directory = Paths.get("directory/to/be/watched");

        /**
         * Start a thread that will create 2000 files to the selected directory
         * This will occur after waiting 5 seconds.
         */
        new Thread(new Runnable()
        {
            @Override
            public void run() 
            {
                try 
                {
                    Thread.sleep(5000);         
                    System.out.println("Copying 2000 files to directory: " + directory);
                    for(int i = 0; i < 2000; i++)
                    {
                        final PrintWriter writer = new PrintWriter(directory.resolve("test_file_" + i + ".txt").toFile(), "UTF-8");
                        writer.println("The first line");
                        writer.println("The second line");
                        writer.close();
                    }
                    System.out.println("Finished copying files to directory: " + directory);
                } 
                catch (final Exception e) 
                {
                    e.printStackTrace();
                } 
            }
        }).start();

        /**
         * Start the watch service polling every 10 seconds
         */
        new DirectoryWatcherExample().startWatchService(directory);
    }

    public void startWatchService(final Path directory) throws InterruptedException, IOException
    {
        final WatchService watchService = FileSystems.getDefault().newWatchService();
        directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);

        while(true)
        {
            System.out.println("Start polling");
            final WatchKey key = watchService.poll(POLLING_TIME, TimeUnit.SECONDS);
            System.out.println("Finished polling and retrieved key");

            if(key != null)
            {
                final Collection<Path> paths = new HashSet<>();
                for (final WatchEvent<?> watchEvent : key.pollEvents())
                {
                    final Path path = ((Path) key.watchable()).resolve((Path) watchEvent.context());
                    paths.add(path);
                    System.out.println("Path added: " + path);
                }

                // Do something with the paths
                addedFiles(paths);

                if (!key.reset())
                {
                    break;
                }   
            }

        }
    }

    // Unimplemented
    public void addedFiles(final Collection<Path> paths)
    {

    }
}

这可能是什么原因造成的?

WatchService.poll(timeout, unit)中的timeout参数不是用来定义延迟多长时间的。它仅定义最长等待时间(之后 returns 是否检测到事件。)

它仍然 returns 一旦它检测到变化。阅读 JavaDoc for WatchService.poll

Retrieves and removes the next watch key, waiting if necessary up to the specified wait time if none are yet present.

没有地方写到它会一直等那么久。

有两种选择:

  1. 您需要在一定时间间隔后通过休眠在 watchService 上调用 poll。正如其他人指出的那样, poll 方法中的超时适用于缓冲区中没有可用事件的情况。此外,由于您没有立即处理事件,因此某些事件可能会溢出操作系统缓冲区并最终丢失。因此,您也需要处理溢出的情况。

  2. 或者,您可能想要使用 Apache Commons IO 文件监控库。它会根据需要轮询文件系统。您甚至可以设置轮询间隔。

参考以下三个class/interface here:

  • FileAlterationMonitor - 它基本上是一个线程(Runnable 实现),它在轮询间隔内休眠并在每个间隔调用后 FileAlterationObserver
  • FileAlterationObserver - 它列出目录中的文件,将当前列表与先前列表进行比较,识别文件更改并在 FileAlterationListener 实现
  • 中调用适当的方法
  • FileAlterationListener - 您需要实现和编写逻辑的接口

您可以考虑为您的用例做的是,在添加或修改文件详细信息时继续将所有文件详细信息添加到列表中。最后,当调用 onStop() 方法时,您使用完整列表调用 addedFiles 方法,清除列表并重新开始。