回顾过往,让让我们一起看看Java 监控文件系统

在这里插入图片描述

简介

1.1 前言

  每当文件发生任何更改时,它们都会自动刷新——这是大多数应用程序中常见的非常普遍的问题。每个应用程序都有一些配置,预期该配置文件中的每次更改都会刷新。解决该问题的过去方法包括使用Thread,根据配置文件的“最后更新时间戳”定期轮询文件更改。Java 从 JDK1.7 开始引入了一项出色的功能:WatchService 类,可用于监控文件系统的变化。WatchService 看作是文件监控器,通过操作系统原生文件系统来运行,可以监控系统是所有文件的变化,这种监控是无需遍历、无需比较的,是一种基于信号收发的监控。

1.2 功能

WatchService是基于本机操作系统实现对文件的监控,动态获取文件变化,无需重启系统。

1.3 应用

  • 比如系统中的配置文件,一般都是系统启动的时候只加载一次,利用WatchService可实现动态修改配置文件,无需重启系统。
  • 监控磁盘中的文件变化,使用 WatchService 进行文件实时监控。

二、关键接口、类和方法

2.1 关键接口

  • java.nio.file.WatchService:监听服务
    • 是JDK的内部服务,它监视注册对象的更改,这些注册的对象必须是Watchable接口的实例。
    • 扩展了Closeable接口,表示可以在需要时关闭服务。通常,应该使用JVM提供的关闭挂钩来完成。
public interface WatchService extends Closeable {
	/**
     *
     */
    void close() throws IOException;
    /**
     * 尝试获取下一个监听结果,如果没有变化则返回null
     */
    WatchKey poll();
    /**
     * 尝试获取下一个监听结果,最多等待指定时间,如果没有变化则返回null
     */
    WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException;
    /**
     * 等待下一次的监听结果,如果没有变化则一直等待
     */
    WatchKey take() throws InterruptedException;
}
  • 监听服务可通过下面方式获取:FileSystems.getDefault().newWatchService();
  • 如果需要长时间一直监控要用take,而如果只是在某个指定的时间监控则用poll
  • java.nio.file.WatchEvent:监听事件
public interface WatchEvent<T> {
    /**
     * 监听事件的类型
     */
    Kind<T> kind();

    /**
     * 事件的个数,大于1表示是一个重复事件
     */
    int count();

    /**
     * 事件的上下文
     * @return 返回触发该事件的那个文件或目录的路径(相对路径)
     */
    T context();
}
  • java.nio.file.WatchKey:监听key
  • WatchKey 对象包含了文件变化的事件的属性集合,也就是所谓的监控信息池,当文件发生变化时所有的变化信息都会被导入监控池。
  • 监控池是静态的,只有当你主动去获取新的监控池时才会有更新的内容加入监控池。这就造成了系统接收到监控信息事件可能稍长的问题。
public interface WatchKey {
    /**
     * 监听key是否合法,true:合法,false:不合法
     */
    boolean isValid();

    /**
     * 拉取并删除位于这个监听key的监听事件列表(可能为空)
     */
    List<WatchEvent<?>> pollEvents();

    /**
     * 监听key复位,复位后,新的pending事件才会再次进入监听,返回true:key合法且复位成功
     * 每次调用 WatchService 的 take() 或 poll() 方法时需要通过本方法重置
     */
    boolean reset();

    /**
     * 取消key的监听服务
     */
    void cancel();

    /**
     * 返回该key关联的Watchable
     */
    Watchable watchable();
}
  • java.nio.file.Watchable:监听注册
public interface Watchable {

    /**
     * 监听注册,包含监听服务、监听事件、监听修改
     */
    WatchKey register(WatchService watcher,WatchEvent.Kind<?>[] events,WatchEvent.Modifier... modifiers)
        throws IOException;


    /**
     * 监听注册,包含监听服务和监听事件
     */
    WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events)  throws IOException;
}

2.2 关键类

https://blog.csdn.net/weixin_38569499/article/details/84992970

  • java.nio.file.FileSystems
方法说明
FileSystem getDefault()返回默认的文件系统,
FileSystem getFileSystem(URI uri)返回一个指向已存在的FileSystem的引用。
如果找不到对应的已存在的FileSystem,则会抛出异常ProviderNotFoundException。
FileSystem newFileSystem(Path path,ClassLoader loader)构建一个文件系统去访问path指定的文件的内容。
FileSystem newFileSystem(URI uri, Map<String,?> env)根据URI创建一个信息文件系统。
如果找不到对应的已存在的FileSystem,则会抛出异常ProviderNotFoundException。
FileSystem newFileSystem(URI uri, Map<String,?> env, ClassLoader loader)同上。
  • java.nio.file.FileSystem

方法说明
Iterable getFileStores()返回一个Iterable,用来遍历该文件系统的各个FileStore
Path getPath(String first, String… more)将字符串拼接成一个路径,并根据路径生成Path的实例
PathMatcher getPathMatcher(String syntaxAndPattern)返回一个 PathMatcher
Iterable getRootDirectories() 返回一个Iterable ,用来遍历根目录的路径
String getSeparator()返回名称分隔符,字符串形式
boolean isOpen()判断文件系统是否已经打开了
boolean isReadOnly()判断这个文件系统的file stores是只读的
WatchService newWatchService()构建了一种新的 WatchService(可选操作)
FileSystemProvider provider()返回创建此文件系统的提供者
void close()关闭此文件系统。如果文件系统已经关闭,则调用该方法没有效果
  • java.nio.file.Path
    • JDK1.7中定义的接口,主要用来在文件系统中定位文件,通常表示系统相关的文件路径。
    • java中的Path表示文件系统的路径,可以指向文件或文件夹,也有相对路径和绝对路径之分。绝对路径表示从文件系统的根路径到文件或是文件夹的路径,而相对路径表示从特定路径下访问指定文件或文件夹的路径。
    • 在很多方面,java.nio.file.Pathjava.io.File 有相似性,但也有一些细微的差别。在很多情况下,可以用Path来代替File类。
方法说明
boolean endsWith(Path other)
boolean endsWith(String other)
Path getFileName()获取文件、目录或者其他类型文件的名称
FileSystem getFileSystem()获取文件系统
Path getName(int index)循环遍历每个元素的名字
int getNameCount()获取路径层级的个数
Path getParent()获取文件的父路径的Path实例
Path getRoot()获取根路径
boolean isAbsolute()判断是否是绝对路径,即根据该路径是否能定位到实际的文件。
需要注意的是,如果是绝对路径,即使文件不存在也会返回true
Iterator iterator() 返回迭代器,用来访问各级路径
Path normalize()规范化文件路径,去除路径中多余的部分,指向真正的路径目录地址
Path relativize(Path other)返回一个相对路径,是基于path的path1的相对路径
Path resolve(Path other)把当前路径当成父路径,把输入参数的路径当成子路径,得到一个新的路径。
Path resolve(String other)把当前路径当成父路径,把输入参数的路径当成子路径,得到一个新的路径。
Path resolveSibling(Path other)会根据给定的路径去替换当前的路径
Path resolveSibling(String other)会根据给定的路径去替换当前的路径
boolean startsWith(Path other)
boolean startsWith(String other)
Path subpath(int beginIndex, int endIndex)获取子路径
Path toAbsolutePath()由相对路径转换成绝对路径
File toFile()转换成File对象
Path toRealPath(LinkOption… options)转换成真实路径
URI toUri()
  • java.nio.file.Paths
    • 是JDK1.7中定义的静态工具类,用来根据String格式的路径或者URI返回Path的实例
方法说明
Path get(URI uri)创建Path实例
Path get(String first, String… more)接受一个或多个字符串,字符串之间自动使用默认文件系统的路径分隔符连接起来。
(Unix是 /,Windows是 \ )
  • 使用相对路径时,可以使用两种符号:
    • .:表示当前路径
    • : 表示父类目录

三、示例

3.1 注意的点

  • Paths.get(path).register 方法只会监视 path 文件下的文件变化,其子目录的变化是不会监视的。
  • 每次take()\poll()操作都会导致线程监控阻塞,每次操作文件可能需要长时间,如果监听目录下有其他事件发生,将会导致事件丢失。
  • WatchKey 每次读完文件变化后,需要调用 reset() 方法才能继续读取变化。

3.2 示例

整个监控目录文件操作的流程大致如下:

  1. 获取 WatchService
  2. 注册指定目录的监视器 WatchService
  3. 等待目录下的文件发生变化
  4. 对发生变化的文件进行操作

下面示例展示监视目录和其子目录下的文件变化,并打印变化信息:

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;
import java.util.*;

import static com.sun.jmx.mbeanserver.Util.cast;

/**
 * 把今天最好的表现当作明天最新的起点..~
 * <p>
 * Today the best performance as tomorrow newest starter!
 *
 * @类描述: Java 从 JDK1.7 开始增加了 WatchService 类,可用于监控文件系统的变化。
 * @author: <a href="mailto:duleilewuhen@sina.com">独泪了无痕</a>
 * @创建时间: 2021-07-22 下午9:25
 * @版本: V 1.0.1
 * @since: JDK 1.8
 */
public final class WatchServiceHelper {
	/**
	 * Java监听文件
	 *
	 * @param rootPath 监听的文件目录(只能监听目录)
	 */
	public static void watcherFile(String rootPath) throws Exception {
		File root = new File(rootPath);
		if (!root.isDirectory()) {
			throw new Exception("只监视目录变化");
		}
// WatchService 就类似一个文件监视器,使用如下方法创建一个监听服务
		WatchService watcher = FileSystems.getDefault().newWatchService();


		// 所有目录集合
		Set<String> pathSet = new LinkedHashSet<>();
		// 递归找子目录
		loopDir(root, pathSet);

		// 维护 WatchKey 与目录的映射,用于找到变化文件的目录。
		Map<WatchKey, String> watchKeyPathMap = new HashMap<>();

		// 创建完监视器后,需要将其绑定到某个目录,并指定监控哪些变化
		Kind[] kinds = {
				// 新增或目录重命名
				StandardWatchEventKinds.ENTRY_CREATE,
				// 修改
				StandardWatchEventKinds.ENTRY_MODIFY,
				// 删除或重命名
				StandardWatchEventKinds.ENTRY_DELETE
		};
		// 遍历添加监视
		for (String path : pathSet) {
			// 监听注册,监听实体的创建、修改、删除事件
			WatchKey key = Paths.get(path).register(watcher, kinds);
			watchKeyPathMap.put(key, path);
		}

		while (true) {
			// 获取下一个文件改动事件
			WatchKey watchKey = watcher.take();
			// 监听key为null,则跳过
			if (watchKey == null) {
				continue;
			}

			// 利用 key.pollEvents() 方法获取监听事件列表
			List<WatchEvent<?>> watchEventList = watchKey.pollEvents();

			// 获取监听事件
			watchEventList.forEach(watchEvent -> {
				// 获取监听事件类型
				Kind kind = watchEvent.kind();
				// 若是异常事件跳过
				if (StandardWatchEventKinds.OVERFLOW != kind) {
					// 获取监听的文件/目录的名称
					Path path = cast(watchEvent.context());
					// 输出事件类型、文件路径及名称
					String msg = String.format("变化类型:%s,监视目录:%s,变化对象:%s",
							kind.name(),
							watchKeyPathMap.get(watchKey),
							path.toString());
					System.out.println(msg);
				}
			});

			// 处理监听key后(即处理监听事件后),监听key需要复位,便于下次监听
			boolean valid = watchKey.reset();
			// 如果重设失败,退出监听
			if (!valid) {
				break;
			}
		}

		// 增加jvm关闭的钩子来关闭监听
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
			try {
				watcher.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}));
	}

	/**
	 * 递归寻找子目录
	 *
	 * @param parent  父目录
	 * @param pathSet 路径集合
	 */
	private static void loopDir(File parent, Set<String> pathSet) {
		if (!parent.isDirectory()) {
			return;
		}
		pathSet.add(parent.getPath());
		for (File child : Objects.requireNonNull(parent.listFiles())) {
			loopDir(child, pathSet);
		}
	}
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独泪了无痕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值