WatchService监听目录后,File.listFiles()无法获取新建文件
问题:WatchService监听目录后,listFiles()无法获取新创建的文件
代码实现
我实现了一个Spring组件,通过WatchService监听目录的文件创建、更新和删除事件,简化逻辑为打印文件名,代码如下:
@Component public class MyComponent { @Autowired MyComponentWatcher myComponentWatcher; @PostConstruct public void init(){ myComponentWatcher.registerWatchService(); } } @Component public class MyComponentWatcher { @Value("${somePath}") private String myPath; private File myDir; @PostConstruct public void init() { this.myDir = new File(myPath); } @Async public void registerWatchService() throws IOException, InterruptedException { WatchService watchService = FileSystems.getDefault().newWatchService(); Path path = myDir.toPath(); path.register( watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); WatchKey key; while ((key = watchService.take()) != null) { for (WatchEvent<?> event : key.pollEvents()) { System.out.println( "Event kind:" + event.kind() + ". File affected: " + event.context() + "."); } key.reset(); File[] files = myDir.listFiles(); System.out.println(files); // 此处预期返回新旧文件,但实际仅返回旧文件 } } }
测试场景与异常现象
- 初始目录含1个文件,使用
Files.write(filePath, fileContent.getBytes(), CREATE_NEW);创建新文件 - 预期:
System.out.println(files)返回新旧文件,实际仅返回旧文件 - 监听器触发事件列表:
- [ENTRY_CREATE, ENTRY_MODIFY]
- [ENTRY_MODIFY]
- [ENTRY_MODIFY]
- [ENTRY_DELETE](来自@AfterEach的删除操作)
原因分析
- File对象的缓存特性:初始化时创建的
myDir对象会缓存目录内容,不会自动刷新,调用listFiles()时读取的是旧缓存。 - 文件系统缓存延迟:
File.listFiles()依赖本地文件系统缓存,新文件创建后缓存未及时更新,无法立即获取到新文件。 - 事件触发时机:
ENTRY_CREATE在文件刚创建(空文件)时触发,此时Files.write可能还在写入内容,文件状态未完全稳定,导致listFiles()无法识别。
解决方法
方法1:刷新目录缓存,重新获取File对象
避免使用初始化时的myDir,每次需要获取目录文件时重新创建File对象,绕过缓存:
// 替换原有的File[] files = myDir.listFiles(); File refreshedDir = new File(myPath); // 重新获取目录对象,强制读取最新状态 File[] files = refreshedDir.listFiles(); System.out.println(Arrays.toString(files)); // 打印文件数组内容,而非对象地址
方法2:使用NIO的Files.list()替代File.listFiles()
NIO的API更贴近底层文件系统,能实时获取目录内容,避免缓存问题:
try (Stream<Path> pathStream = Files.list(path)) { List<String> fileNames = pathStream .map(p -> p.getFileName().toString()) .collect(Collectors.toList()); System.out.println("当前目录文件:" + fileNames); } catch (IOException e) { e.printStackTrace(); }
方法3:基于WatchEvent直接处理目标文件
无需遍历整个目录,直接从事件的context()获取文件名,结合目录路径构建完整文件路径进行操作:
for (WatchEvent<?> event : key.pollEvents()) { Path eventPath = (Path) event.context(); Path fullPath = path.resolve(eventPath); System.out.println("Event kind:" + event.kind() + ". File affected: " + fullPath + "."); // 验证文件是否存在 if(Files.exists(fullPath)){ System.out.println("文件已存在:" + fullPath.getFileName()); } }
关于多次事件触发的说明
Files.write创建文件时触发多次ENTRY_MODIFY是正常现象:
- 第一次
ENTRY_CREATE+ENTRY_MODIFY:创建空文件 - 后续
ENTRY_MODIFY:写入文件内容的过程中,文件内容/属性变更触发 ENTRY_DELETE:测试用例的@AfterEach删除文件触发
内容的提问来源于stack exchange,提问作者Nivia
相关产品推荐
相关产品推荐

