java.nio.file.WatchService 實時監控文件變化的示例代碼
在平時的開發過程中,會有很多場景需要實時監聽文件的變化,如下:
1、通過實時監控 mysql 的 binlog 日志實現數據同步
2、修改配置文件後,希望系統可以實時感知
3、應用系統將日志寫入文件中,日志監控系統可以實時抓取日志,分析日志內容並進行報警
4、類似 ide 工具,可以實時感知管理的工程下的文件變更
在 Java 語言中,從 JDK7 開始,新增瞭java.nio.file.WatchService
類,用來實時監控文件的變化。
1.示例代碼
FileWatchedService 類:
package org.learn.file; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.List; /** * 實時監控文件的變化 * * @author zhibo * @date 2019-07-30 20:37 */ public class FileWatchedService { private WatchService watchService; private FileWatchedListener listener; /** * * @param path 要監聽的目錄,註意該 Path 隻能是目錄,否則會報錯 java.nio.file.NotDirectoryException: /Users/zhibo/logs/a.log * @param listener 自定義的 listener,用來處理監聽到的創建、修改、刪除事件 * @throws IOException */ public FileWatchedService(Path path, FileWatchedListener listener) throws IOException { watchService = FileSystems.getDefault().newWatchService(); path.register(watchService, /// 監聽文件創建事件 StandardWatchEventKinds.ENTRY_CREATE, /// 監聽文件刪除事件 StandardWatchEventKinds.ENTRY_DELETE, /// 監聽文件修改事件 StandardWatchEventKinds.ENTRY_MODIFY); // // path.register(watchService, // new WatchEvent.Kind[]{ // StandardWatchEventKinds.ENTRY_MODIFY, // StandardWatchEventKinds.ENTRY_CREATE, // StandardWatchEventKinds.ENTRY_DELETE // }, // SensitivityWatchEventModifier.HIGH); this.listener = listener; } private void watch() throws InterruptedException { while (true) { WatchKey watchKey = watchService.take(); List<WatchEvent<?>> watchEventList = watchKey.pollEvents(); for (WatchEvent<?> watchEvent : watchEventList) { WatchEvent.Kind kind = watchEvent.kind(); WatchEvent<Path> curEvent = (WatchEvent<Path>) watchEvent; if (kind == StandardWatchEventKinds.OVERFLOW) { listener.onOverflowed(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) { listener.onCreated(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { listener.onModified(curEvent); continue; } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { listener.onDeleted(curEvent); continue; } } /** * WatchKey 有兩個狀態: * {@link sun.nio.fs.AbstractWatchKey.State.READY ready} 就緒狀態:表示可以監聽事件 * {@link sun.nio.fs.AbstractWatchKey.State.SIGNALLED signalled} 有信息狀態:表示已經監聽到事件,不可以接續監聽事件 * 每次處理完事件後,必須調用 reset 方法重置 watchKey 的狀態為 ready,否則 watchKey 無法繼續監聽事件 */ if (!watchKey.reset()) { break; } } } public static void main(String[] args) { try { Path path = Paths.get("/Users/zhibo/logs/"); FileWatchedService fileWatchedService = new FileWatchedService(path, new FileWatchedAdapter()); fileWatchedService.watch(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }
FileWatchedListener 類:
package org.learn.file; import java.nio.file.Path; import java.nio.file.WatchEvent; public interface FileWatchedListener { void onCreated(WatchEvent<Path> watchEvent); void onDeleted(WatchEvent<Path> watchEvent); void onModified(WatchEvent<Path> watchEvent); void onOverflowed(WatchEvent<Path> watchEvent); }
FileWatchedAdapter 類:
package org.learn.file; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; /** * 文件監聽適配器 * * @author zhibo * @date 2019-07-31 11:07 */ public class FileWatchedAdapter implements FileWatchedListener { @Override public void onCreated(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被創建,時間:%s", fileName, now())); } @Override public void onDeleted(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被刪除,時間:%s", fileName, now())); } @Override public void onModified(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被修改,時間:%s", fileName, now())); } @Override public void onOverflowed(WatchEvent<Path> watchEvent) { Path fileName = watchEvent.context(); System.out.println(String.format("文件【%s】被丟棄,時間:%s", fileName, now())); } private String now(){ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); return dateFormat.format(Calendar.getInstance().getTime()); } }
執行以上代碼,啟動監控任務,然後我在/Users/zhibo/logs/
目錄中創建、修改、刪除文件,命令如下:
應用程序感知到文件變化,打印日志如下:
2.其實並沒有實時
大傢可以看到,監控任務基本上是以 10 秒為單位進行日志打印的,也就是說修改一個文件,WatchService 10秒之後才能感知到文件的變化,沒有想象中的那麼實時。根據以上的經驗,推測可能是 WatchService 做瞭定時的操作,時間間隔為 10 秒。通過翻閱源代碼發現,在 PollingWatchService
中確實存在一個固定時間間隔的調度器,如下圖:
該調度器的時間間隔有 SensitivityWatchEventModifier
進行控制,該類提供瞭 3 個級別的時間間隔,分別為2秒、10秒、30秒,默認值為 10秒。SensitivityWatchEventModifier
源碼如下:
package com.sun.nio.file; import java.nio.file.WatchEvent.Modifier; public enum SensitivityWatchEventModifier implements Modifier { HIGH(2), MEDIUM(10), LOW(30); private final int sensitivity; public int sensitivityValueInSeconds() { return this.sensitivity; } private SensitivityWatchEventModifier(int var3) { this.sensitivity = var3; } }
通過改變時間間隔來進行驗證,將
path.register(watchService, /// 監聽文件創建事件 StandardWatchEventKinds.ENTRY_CREATE, /// 監聽文件刪除事件 StandardWatchEventKinds.ENTRY_DELETE, /// 監聽文件修改事件 StandardWatchEventKinds.ENTRY_MODIFY);
修改為:
path.register(watchService, new WatchEvent.Kind[]{ StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE }, SensitivityWatchEventModifier.HIGH);
查看日志,發現正如我們的推斷,WatchService 正以每 2 秒的時間間隔感知文件變化。
在 stackoverflow 中也有人提出瞭該問題,問題:Is Java 7 WatchService Slow for Anyone Else,我的 mac 系統中確實存在該問題,由於手頭沒有 windows、linux 系統,因此無法進行這兩個系統的驗證。
到此這篇關於java.nio.file.WatchService 實時監控文件變化的文章就介紹到這瞭,更多相關java.nio.file.WatchService 監控文件變化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java實現解析zip壓縮包並獲取文件內容
- java導出csv格式文件的方法
- Java如何通過File類方法刪除指定文件夾中的全部文件
- Springboot 上傳文件或頭像(MultipartFile、transferTo)
- Android如何監測文件夾內容變化詳解