<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在研究規則引擎時,如果規則以檔案的形式儲存,那麼就需要監聽指定的目錄或檔案來感知規則是否變化,進而進行載入。當然,在其他業務場景下,比如想實現組態檔的動態載入、紀錄檔檔案的監聽、FTP檔案變動監聽等都會遇到類似的場景。
本文給大家提供三種解決方案,並分析其中的利弊,建議收藏,以備不時之需。
這個方案是最簡單,最能直接想到的解決方案。通過定時任務,輪訓查詢檔案的最後修改時間,與上一次進行對比。如果發生變化,則說明檔案已經修改,進行重新載入或對應的業務邏輯處理。
在上篇文章《JDK的一個Bug,監聽檔案變更要小心了》中已經編寫了具體的範例,並且也提出了其中的不足。
這裡再把範例程式碼貼出來:
public class FileWatchDemo { /** * 上次更新時間 */ public static long LAST_TIME = 0L; public static void main(String[] args) throws IOException { String fileName = "/Users/zzs/temp/1.txt"; // 建立檔案,僅為範例,實踐中由其他程式觸發檔案的變更 createFile(fileName); // 執行2次 for (int i = 0; i < 2; i++) { long timestamp = readLastModified(fileName); if (timestamp != LAST_TIME) { System.out.println("檔案已被更新:" + timestamp); LAST_TIME = timestamp; // 重新載入,檔案內容 } else { System.out.println("檔案未更新"); } } } public static void createFile(String fileName) throws IOException { File file = new File(fileName); if (!file.exists()) { boolean result = file.createNewFile(); System.out.println("建立檔案:" + result); } } public static long readLastModified(String fileName) { File file = new File(fileName); return file.lastModified(); } }
對於檔案低頻變動的場景,這種方案實現簡單,基本上可以滿足需求。不過像上篇文章中提到的那樣,需要注意Java 8和Java 9中File#lastModified的Bug問題。
但該方案如果用在檔案目錄的變化上,缺點就有些明顯了,比如:操作頻繁,效率都損耗在遍歷、儲存狀態、對比狀態上了,無法充分利用OS的功能。
在Java 7中新增了java.nio.file.WatchService
,通過它可以實現檔案變動的監聽。WatchService是基於作業系統的檔案系統監控器,可以監控系統所有檔案的變化,無需遍歷、無需比較,是一種基於訊號收發的監控,效率高。
public class WatchServiceDemo { public static void main(String[] args) throws IOException { // 這裡的監聽必須是目錄 Path path = Paths.get("/Users/zzs/temp/"); // 建立WatchService,它是對作業系統的檔案監視器的封裝,相對之前,不需要遍歷檔案目錄,效率要高很多 WatchService watcher = FileSystems.getDefault().newWatchService(); // 註冊指定目錄使用的監聽器,監視目錄下檔案的變化; // PS:Path必須是目錄,不能是檔案; // StandardWatchEventKinds.ENTRY_MODIFY,表示監視檔案的修改事件 path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); // 建立一個執行緒,等待目錄下的檔案發生變化 try { while (true) { // 獲取目錄的變化: // take()是一個阻塞方法,會等待監視器發出的訊號才返回。 // 還可以使用watcher.poll()方法,非阻塞方法,會立即返回當時監視器中是否有訊號。 // 返回結果WatchKey,是一個單例物件,與前面的register方法返回的範例是同一個; WatchKey key = watcher.take(); // 處理檔案變化事件: // key.pollEvents()用於獲取檔案變化事件,只能獲取一次,不能重複獲取,類似佇列的形式。 for (WatchEvent<?> event : key.pollEvents()) { // event.kind():事件型別 if (event.kind() == StandardWatchEventKinds.OVERFLOW) { //事件可能lost or discarded continue; } // 返回觸發事件的檔案或目錄的路徑(相對路徑) Path fileName = (Path) event.context(); System.out.println("檔案更新: " + fileName); } // 每次呼叫WatchService的take()或poll()方法時需要通過本方法重置 if (!key.reset()) { break; } } } catch (Exception e) { e.printStackTrace(); } } } 複製程式碼
上述demo展示了WatchService的基本使用方式,註解部分也說明了每個API的具體作用。
通過WatchService監聽檔案的型別也變得更加豐富:
如果檢視WatchService實現類(PollingWatchService)的原始碼,會發現,本質上就是開啟了一個獨立的執行緒來監控檔案的變化:
PollingWatchService() { // TBD: Make the number of threads configurable scheduledExecutor = Executors .newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(null, r, "FileSystemWatcher", 0, false); t.setDaemon(true); return t; }}); }
也就是說,本來需要我們手動實現的部分,也由WatchService內部幫我們完成了。
如果你編寫一個demo,進行驗證時,會很明顯的感覺到WatchService監控檔案的變化並不是實時的,有時候要等幾秒才監聽到檔案的變化。以實現類PollingWatchService為例,檢視原始碼,可以看到如下程式碼:
void enable(Set<? extends Kind<?>> var1, long var2) { synchronized(this) { this.events = var1; Runnable var5 = new Runnable() { public void run() { PollingWatchKey.this.poll(); } }; this.poller = PollingWatchService.this.scheduledExecutor.scheduleAtFixedRate(var5, var2, var2, TimeUnit.SECONDS); } }
也就是說監聽器由按照固定時間間隔的排程器來控制的,而這個時間間隔在SensitivityWatchEventModifier類中定義:
public enum SensitivityWatchEventModifier implements Modifier { HIGH(2), MEDIUM(10), LOW(30); // ... }
該類提供了3個級別的時間間隔,分別為2秒、10秒、30秒,預設值為10秒。這個時間間隔可以在path#register時進行傳遞:
path.register(watcher, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
相對於方案一,實現起來簡單,效率高。不足的地方也很明顯,只能監聽當前目錄下的檔案和目錄,不能監視子目錄,而且我們也看到監聽只能算是準實時的,而且監聽時間只能取API預設提供的三個值。
該API在Stack Overflow上也有人提出Java 7在Mac OS下有延遲的問題,甚至涉及到Windows和Linux系統,筆者沒有進行其他作業系統的驗證,如果你遇到類似的問題,可參考對應的文章,尋求解決方案:https://www.jb51.net/article/249820.htm。
方案一我們自己來實現,方案二藉助於JDK的API來實現,方案三便是藉助於開源的框架來實現,這就是幾乎每個專案都會引入的commons-io類庫。
引入相應依賴:
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.7</version> </dependency>
注意,不同的版本需要不同的JDK支援,2.7需要Java 8及以上版本。
commons-io對實現檔案監聽的實現位於org.apache.commons.io.monitor包下,基本使用流程如下:
FileAlterationListenerAdaptor
實現對檔案與目錄的建立、修改、刪除事件的處理;FileAlterationObserver
;第一步:建立檔案監聽器。根據需要在不同的方法內實現對應的業務邏輯處理。
public class FileListener extends FileAlterationListenerAdaptor { @Override public void onStart(FileAlterationObserver observer) { super.onStart(observer); System.out.println("onStart"); } @Override public void onDirectoryCreate(File directory) { System.out.println("新建:" + directory.getAbsolutePath()); } @Override public void onDirectoryChange(File directory) { System.out.println("修改:" + directory.getAbsolutePath()); } @Override public void onDirectoryDelete(File directory) { System.out.println("刪除:" + directory.getAbsolutePath()); } @Override public void onFileCreate(File file) { String compressedPath = file.getAbsolutePath(); System.out.println("新建:" + compressedPath); if (file.canRead()) { // TODO 讀取或重新載入檔案內容 System.out.println("檔案變更,進行處理"); } } @Override public void onFileChange(File file) { String compressedPath = file.getAbsolutePath(); System.out.println("修改:" + compressedPath); } @Override public void onFileDelete(File file) { System.out.println("刪除:" + file.getAbsolutePath()); } @Override public void onStop(FileAlterationObserver observer) { super.onStop(observer); System.out.println("onStop"); } }
第二步:封裝一個檔案監控的工具類,核心就是建立一個觀察者FileAlterationObserver,將檔案路徑Path和監聽器FileAlterationListener進行封裝,然後交給FileAlterationMonitor。
public class FileMonitor { private FileAlterationMonitor monitor; public FileMonitor(long interval) { monitor = new FileAlterationMonitor(interval); } /** * 給檔案新增監聽 * * @param path 檔案路徑 * @param listener 檔案監聽器 */ public void monitor(String path, FileAlterationListener listener) { FileAlterationObserver observer = new FileAlterationObserver(new File(path)); monitor.addObserver(observer); observer.addListener(listener); } public void stop() throws Exception { monitor.stop(); } public void start() throws Exception { monitor.start(); } }
第三步:呼叫並執行:
public class FileRunner { public static void main(String[] args) throws Exception { FileMonitor fileMonitor = new FileMonitor(1000); fileMonitor.monitor("/Users/zzs/temp/", new FileListener()); fileMonitor.start(); } }
執行程式,會發現每隔1秒輸入一次紀錄檔。當檔案發生變更時,也會列印出對應的紀錄檔:
onStart
修改:/Users/zzs/temp/1.txt
onStop
onStart
onStop
當然,對應的監聽時間間隔,可以通過在建立FileMonitor時進行修改。
該方案中監聽器本身會啟動一個執行緒定時處理。在每次執行時,都會先呼叫事件監聽處理類的onStart方法,然後檢查是否有變動,並呼叫對應事件的方法;比如,onChange檔案內容改變,檢查完後,再呼叫onStop方法,釋放當前執行緒佔用的CPU資源,等待下次間隔時間到了被再次喚醒執行。
監聽器是基於檔案目錄為根源的,也可以可以設定過濾器,來實現對應檔案變動的監聽。過濾器的設定可檢視FileAlterationObserver的構造方法:
public FileAlterationObserver(String directoryName, FileFilter fileFilter, IOCase caseSensitivity) { this(new File(directoryName), fileFilter, caseSensitivity); }
至此,基於Java實現監聽檔案變化的三種方案便介紹完畢。經過上述分析及範例,大家已經看到,並沒有完美的解決方案,根據自己的業務情況及系統的容忍度可選擇最適合的方案。而且,在此基礎上可以新增一些其他的輔助措施,來避免具體方案中的不足之處。
到此這篇關於Java實現監聽檔案變化的三種方法的文章就介紹到這了,更多相關Java監聽檔案變化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45