最推荐、最高效的方式是使用 watchservice(java nio.2 提供),它基于操作系统原生事件通知机制,避免轮询开销;需注册目标目录、循环 take() 等待事件、遍历处理并显式 reset(),子目录需递归注册,重命名被拆为 delete+create,须注意资源关闭与异常防护。

Java 中监控文件夹变化,最推荐、最高效的方式是使用 WatchService(Java NIO.2 提供),它基于操作系统原生事件通知机制,避免轮询开销。
核心步骤:注册 + 等待 + 处理 + 重置
WatchService 不是“一注册就永久生效”,而是一套需要主动维护的事件循环机制:
- 调用
FileSystems.getDefault().newWatchService()创建服务实例 - 用
Path.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)注册**目标目录本身**(注意:只监控该目录下一级,不递归) - 在
while (true)循环中调用watcher.take()阻塞等待事件触发(返回一个WatchKey) - 遍历
key.pollEvents()获取所有待处理事件,通过event.kind()和event.context()判断类型与文件名 -
必须显式调用
key.reset(),否则该 key 不会再次触发新事件;且需检查返回值:if (!key.reset()) break;(路径已失效时返回 false)
注意子目录不会自动监听,需手动递归注册
WatchService 默认只响应注册路径的直接子项变动。例如注册 /a,那么 /a/b.txt 的修改能捕获,但 /a/sub/c.txt 的修改完全静默。
- 要用
Files.walkFileTree()配合SimpleFileVisitor遍历所有子目录 - 对每个遍历到的
Path单独调用register() - 避开符号链接(
LinkOption.NOFOLLOW_LINKS),防止死循环 - 注册后若某子目录被删除或移动,对应 WatchKey 将失效,不会自动恢复
重命名和移动操作没有独立事件
Java 没有 ENTRY_RENAME 类型。系统底层的 rename 操作,在 WatchService 层被拆解为两个分离事件:
- 原路径触发
ENTRY_DELETE - 新路径触发
ENTRY_CREATE - 两者无原子性、无关联标识,时间戳可能极接近但无法保证顺序
- 如需识别重命名,只能靠额外手段近似判断(如文件大小 + 修改时间 + 内容哈希),但存在竞态风险
别忘了资源清理和异常防护
WatchService 关联操作系统资源,必须显式关闭:
- 用 try-with-resources 或 finally 块确保
watcher.close() - 不要在
ClosedWatchServiceException后还调用key.reset(),会抛IllegalStateException - 多线程环境下,一个
WatchKey不应被多个线程并发 reset - 高频小文件场景(如日志写入)可能丢事件,不适合强一致性要求的采集任务
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











