原文: https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/

在此示例中,我们将学习使用 Java 8 WatchService API 观察目录及其中的所有子目录和文件。

如何注册 Java 8 WatchService

要注册WatchService,请获取目录路径并使用path.register()方法。

  1. Path path = Paths.get(".");
  2. WatchService watchService = path.getFileSystem().newWatchService();
  3. path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

观察变化事件

要获取目录及其中文件的更改,请使用watchKey.pollEvents()方法以流的形式返回所有更改事件的集合。

  1. WatchKey watchKey = null;
  2. while (true) {
  3. watchKey = watchService.poll(10, TimeUnit.MINUTES);
  4. if(watchKey != null) {
  5. watchKey.pollEvents().stream().forEach(event -> System.out.println(event.context()));
  6. }
  7. watchKey.reset();
  8. }

该密钥一直有效,直到:

  • 通过调用其cancel方法显式地取消它,或者
  • 隐式取消,因为该对象不再可访问,或者
  • 通过关闭观察服务。

如果您要重复使用同一键在一个循环中多次获取更改事件,请不要忘记调用watchKey.reset()方法,该方法会将键再次设置为就绪状态。

请注意,诸如如何检测事件,其及时性以及是否保留其顺序之类的几件事高度依赖于底层操作系统。 某些更改可能导致一个操作系统中的单个条目,而类似的更改可能导致另一操作系统中的多个事件。

监视目录,子目录和文件中的更改示例

在此示例中,我们将看到一个观看目录的示例,该目录中包含所有子目录和文件。 我们将维护监视键和目录Map<WatchKey, Path> keys的映射,以正确识别已修改的目录。

下面的方法将向观察者注册一个目录,然后将目录和密钥存储在映射中。

  1. private void registerDirectory(Path dir) throws IOException
  2. {
  3. WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
  4. keys.put(key, dir);
  5. }

在遍历目录结构并为遇到的每个目录调用此方法时,将递归调用此方法。

  1. private void walkAndRegisterDirectories(final Path start) throws IOException {
  2. // register directory and sub-directories
  3. Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
  4. @Override
  5. public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
  6. registerDirectory(dir);
  7. return FileVisitResult.CONTINUE;
  8. }
  9. });
  10. }

请注意,无论何时创建新目录,我们都会在观察服务中注册该目录,并将新密钥添加到映射中。

  1. WatchEvent.Kind kind = event.kind();
  2. if (kind == ENTRY_CREATE) {
  3. try {
  4. if (Files.isDirectory(child)) {
  5. walkAndRegisterDirectories(child);
  6. }
  7. } catch (IOException x) {
  8. // do something useful
  9. }
  10. }

将以上所有内容与处理事件的逻辑放在一起,完整的示例如下所示:

  1. package com.howtodoinjava.examples;
  2. import static java.nio.file.StandardWatchEventKinds.*;
  3. import java.io.IOException;
  4. import java.nio.file.FileSystems;
  5. import java.nio.file.FileVisitResult;
  6. import java.nio.file.Files;
  7. import java.nio.file.Path;
  8. import java.nio.file.Paths;
  9. import java.nio.file.SimpleFileVisitor;
  10. import java.nio.file.WatchEvent;
  11. import java.nio.file.WatchKey;
  12. import java.nio.file.WatchService;
  13. import java.nio.file.attribute.BasicFileAttributes;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. public class Java8WatchServiceExample {
  17. private final WatchService watcher;
  18. private final Map<WatchKey, Path> keys;
  19. /**
  20. * Creates a WatchService and registers the given directory
  21. */
  22. Java8WatchServiceExample(Path dir) throws IOException {
  23. this.watcher = FileSystems.getDefault().newWatchService();
  24. this.keys = new HashMap<WatchKey, Path>();
  25. walkAndRegisterDirectories(dir);
  26. }
  27. /**
  28. * Register the given directory with the WatchService; This function will be called by FileVisitor
  29. */
  30. private void registerDirectory(Path dir) throws IOException
  31. {
  32. WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
  33. keys.put(key, dir);
  34. }
  35. /**
  36. * Register the given directory, and all its sub-directories, with the WatchService.
  37. */
  38. private void walkAndRegisterDirectories(final Path start) throws IOException {
  39. // register directory and sub-directories
  40. Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
  41. @Override
  42. public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
  43. registerDirectory(dir);
  44. return FileVisitResult.CONTINUE;
  45. }
  46. });
  47. }
  48. /**
  49. * Process all events for keys queued to the watcher
  50. */
  51. void processEvents() {
  52. for (;;) {
  53. // wait for key to be signalled
  54. WatchKey key;
  55. try {
  56. key = watcher.take();
  57. } catch (InterruptedException x) {
  58. return;
  59. }
  60. Path dir = keys.get(key);
  61. if (dir == null) {
  62. System.err.println("WatchKey not recognized!!");
  63. continue;
  64. }
  65. for (WatchEvent<?> event : key.pollEvents()) {
  66. @SuppressWarnings("rawtypes")
  67. WatchEvent.Kind kind = event.kind();
  68. // Context for directory entry event is the file name of entry
  69. @SuppressWarnings("unchecked")
  70. Path name = ((WatchEvent<Path>)event).context();
  71. Path child = dir.resolve(name);
  72. // print out event
  73. System.out.format("%s: %s\n", event.kind().name(), child);
  74. // if directory is created, and watching recursively, then register it and its sub-directories
  75. if (kind == ENTRY_CREATE) {
  76. try {
  77. if (Files.isDirectory(child)) {
  78. walkAndRegisterDirectories(child);
  79. }
  80. } catch (IOException x) {
  81. // do something useful
  82. }
  83. }
  84. }
  85. // reset key and remove from set if directory no longer accessible
  86. boolean valid = key.reset();
  87. if (!valid) {
  88. keys.remove(key);
  89. // all directories are inaccessible
  90. if (keys.isEmpty()) {
  91. break;
  92. }
  93. }
  94. }
  95. }
  96. public static void main(String[] args) throws IOException {
  97. Path dir = Paths.get("c:/temp");
  98. new Java8WatchServiceExample(dir).processEvents();
  99. }
  100. }

运行该程序并在给定输入中更改文件和目录后,您将在控制台中注意到捕获的事件。

  1. Output:
  2. ENTRY_CREATE: c:\temp\New folder
  3. ENTRY_DELETE: c:\temp\New folder
  4. ENTRY_CREATE: c:\temp\data
  5. ENTRY_CREATE: c:\temp\data\New Text Document.txt
  6. ENTRY_MODIFY: c:\temp\data
  7. ENTRY_DELETE: c:\temp\data\New Text Document.txt
  8. ENTRY_CREATE: c:\temp\data\tempFile.txt
  9. ENTRY_MODIFY: c:\temp\data
  10. ENTRY_MODIFY: c:\temp\data\tempFile.txt
  11. ENTRY_MODIFY: c:\temp\data\tempFile.txt
  12. ENTRY_MODIFY: c:\temp\data\tempFile.txt

这就是使用 Java 8 WatchService API 监视文件更改并进行处理的简单示例。

学习愉快!

资源:

WatchService Java 文档

遍历文件树

Java 8 路径

Lambda 表达式