# Java `WatchService` API 教程

> 原文： [https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/](https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/)

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

## 如何注册 Java 8 `WatchService`

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

```java
Path path = Paths.get(".");
WatchService watchService =  path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

```

## 观察变化事件

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

```java
WatchKey watchKey = null;
while (true) {
	watchKey = watchService.poll(10, TimeUnit.MINUTES);
	if(watchKey != null) {
		watchKey.pollEvents().stream().forEach(event -> System.out.println(event.context()));
	}
	watchKey.reset();
}

```

该密钥一直有效，直到：

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

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

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

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

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

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

```java
private void registerDirectory(Path dir) throws IOException 
{
	WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
	keys.put(key, dir);
}

```

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

```java
private void walkAndRegisterDirectories(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 {
			registerDirectory(dir);
			return FileVisitResult.CONTINUE;
		}
	});
}

```

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

```java
WatchEvent.Kind kind = event.kind();
if (kind == ENTRY_CREATE) {
	try {
		if (Files.isDirectory(child)) {
			walkAndRegisterDirectories(child);
		}
	} catch (IOException x) {
		// do something useful
	}
}

```

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

```java
package com.howtodoinjava.examples;

import static java.nio.file.StandardWatchEventKinds.*;

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.Map;

public class Java8WatchServiceExample {

	private final WatchService watcher;
	private final Map<WatchKey, Path> keys;

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

		walkAndRegisterDirectories(dir);
	}

	/**
	 * Register the given directory with the WatchService; This function will be called by FileVisitor
	 */
	private void registerDirectory(Path dir) throws IOException 
	{
		WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		keys.put(key, dir);
	}

	/**
	 * Register the given directory, and all its sub-directories, with the WatchService.
	 */
	private void walkAndRegisterDirectories(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 {
				registerDirectory(dir);
				return FileVisitResult.CONTINUE;
			}
		});
	}

	/**
	 * 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()) {
				@SuppressWarnings("rawtypes")
				WatchEvent.Kind kind = event.kind();

				// Context for directory entry event is the file name of entry
				@SuppressWarnings("unchecked")
				Path name = ((WatchEvent<Path>)event).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 (kind == ENTRY_CREATE) {
					try {
						if (Files.isDirectory(child)) {
							walkAndRegisterDirectories(child);
						}
					} catch (IOException x) {
						// do something useful
					}
				}
			}

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

	public static void main(String[] args) throws IOException {
		Path dir = Paths.get("c:/temp");
		new Java8WatchServiceExample(dir).processEvents();
	}
}

```

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

```java
Output:

ENTRY_CREATE: c:\temp\New folder
ENTRY_DELETE: c:\temp\New folder
ENTRY_CREATE: c:\temp\data
ENTRY_CREATE: c:\temp\data\New Text Document.txt
ENTRY_MODIFY: c:\temp\data
ENTRY_DELETE: c:\temp\data\New Text Document.txt
ENTRY_CREATE: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data
ENTRY_MODIFY: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data\tempFile.txt
ENTRY_MODIFY: c:\temp\data\tempFile.txt
```

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

学习愉快！

资源：

[`WatchService` Java 文档](https://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchService.html)

[遍历文件树](https://docs.oracle.com/javase/tutorial/essential/io/walk.html)

[Java 8 路径](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Paths.html)

[Lambda 表达式](https://en.wikipedia.org/wiki/Lambda)