<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          監(jiān)聽日志文件變化的三種方法,推薦第三種!

          共 4962字,需瀏覽 10分鐘

           ·

          2022-05-31 23:02

          背景

          在研究規(guī)則引擎時,如果規(guī)則以文件的形式存儲,那么就需要監(jiān)聽指定的目錄或文件來感知規(guī)則是否變化,進而進行加載。當然,在其他業(yè)務場景下,比如想實現配置文件的動態(tài)加載、日志文件的監(jiān)聽、FTP文件變動監(jiān)聽等都會遇到類似的場景。

          本文給大家提供三種解決方案,并分析其中的利弊,建議收藏,以備不時之需。

          方案一:定時任務 + File#lastModified

          這個方案是最簡單,最能直接想到的解決方案。通過定時任務,輪訓查詢文件的最后修改時間,與上一次進行對比。如果發(fā)生變化,則說明文件已經修改,進行重新加載或對應的業(yè)務邏輯處理。

          在上篇文章《JDK的一個Bug,監(jiān)聽文件變更要小心了》中已經編寫了具體的實例,并且也提出了其中的不足。

          這里再把實例代碼貼出來:

          public?class?FileWatchDemo?{

          ?/**
          ??*?上次更新時間
          ??*/
          ?public?static?long?LAST_TIME?=?0L;

          ?public?static?void?main(String[]?args)?throws?IOException?{

          ??String?fileName?=?"/Users/zzs/temp/1.txt";
          ??//?創(chuàng)建文件,僅為實例,實踐中由其他程序觸發(fā)文件的變更
          ??createFile(fileName);

          ??//?執(zhí)行2次
          ??for?(int?i?=?0;?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("創(chuàng)建文件:"?+?result);
          ??}
          ?}

          ?public?static?long?readLastModified(String?fileName)?{
          ??File?file?=?new?File(fileName);
          ??return?file.lastModified();
          ?}
          }

          對于文件低頻變動的場景,這種方案實現簡單,基本上可以滿足需求。不過像上篇文章中提到的那樣,需要注意Java 8和Java 9中File#lastModified的Bug問題。

          但該方案如果用在文件目錄的變化上,缺點就有些明顯了,比如:操作頻繁,效率都損耗在遍歷、保存狀態(tài)、對比狀態(tài)上了,無法充分利用OS的功能。

          方案二:WatchService

          在Java 7中新增了java.nio.file.WatchService,通過它可以實現文件變動的監(jiān)聽。WatchService是基于操作系統的文件系統監(jiān)控器,可以監(jiān)控系統所有文件的變化,無需遍歷、無需比較,是一種基于信號收發(fā)的監(jiān)控,效率高。

          public?class?WatchServiceDemo?{

          ?public?static?void?main(String[]?args)?throws?IOException?{
          ??//?這里的監(jiān)聽必須是目錄
          ??Path?path?=?Paths.get("/Users/zzs/temp/");
          ??//?創(chuàng)建WatchService,它是對操作系統的文件監(jiān)視器的封裝,相對之前,不需要遍歷文件目錄,效率要高很多
          ??WatchService?watcher?=?FileSystems.getDefault().newWatchService();
          ??//?注冊指定目錄使用的監(jiān)聽器,監(jiān)視目錄下文件的變化;
          ??// PS:Path必須是目錄,不能是文件;
          ??//?StandardWatchEventKinds.ENTRY_MODIFY,表示監(jiān)視文件的修改事件
          ??path.register(watcher,?StandardWatchEventKinds.ENTRY_MODIFY);

          ??//?創(chuàng)建一個線程,等待目錄下的文件發(fā)生變化
          ??try?{
          ???while?(true)?{
          ????//?獲取目錄的變化:
          ????// take()是一個阻塞方法,會等待監(jiān)視器發(fā)出的信號才返回。
          ????//?還可以使用watcher.poll()方法,非阻塞方法,會立即返回當時監(jiān)視器中是否有信號。
          ????//?返回結果WatchKey,是一個單例對象,與前面的register方法返回的實例是同一個;
          ????WatchKey?key?=?watcher.take();
          ????//?處理文件變化事件:
          ????// key.pollEvents()用于獲取文件變化事件,只能獲取一次,不能重復獲取,類似隊列的形式。
          ????for?(WatchEvent?event?:?key.pollEvents())?{
          ?????// event.kind():事件類型
          ?????if?(event.kind()?==?StandardWatchEventKinds.OVERFLOW)?{
          ??????//事件可能lost?or?discarded
          ??????continue;
          ?????}
          ?????//?返回觸發(fā)事件的文件或目錄的路徑(相對路徑)
          ?????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監(jiān)聽文件的類型也變得更加豐富:

          • ENTRY_CREATE 目標被創(chuàng)建

          • ENTRY_DELETE 目標被刪除

          • ENTRY_MODIFY 目標被修改

          • OVERFLOW 一個特殊的Event,表示Event被放棄或者丟失

          如果查看WatchService實現類(PollingWatchService)的源碼,會發(fā)現,本質上就是開啟了一個獨立的線程來監(jiān)控文件的變化:

          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監(jiān)控文件的變化并不是實時的,有時候要等幾秒才監(jiān)聽到文件的變化。以實現類PollingWatchService為例,查看源碼,可以看到如下代碼:

          void?enable(Set>?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);
          ????????????}
          ????????}

          也就是說監(jiān)聽器由按照固定時間間隔的調度器來控制的,而這個時間間隔在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);

          相對于方案一,實現起來簡單,效率高。不足的地方也很明顯,只能監(jiān)聽當前目錄下的文件和目錄,不能監(jiān)視子目錄,而且我們也看到監(jiān)聽只能算是準實時的,而且監(jiān)聽時間只能取API默認提供的三個值。

          該API在Stack Overflow上也有人提出Java 7在Mac OS下有延遲的問題,甚至涉及到Windows和Linux系統,筆者沒有進行其他操作系統的驗證,如果你遇到類似的問題,可參考對應的文章,尋求解決方案:https://blog.csdn.net/claram/article/details/97919664 。

          方案三:Apache Commons-IO

          方案一我們自己來實現,方案二借助于JDK的API來實現,方案三便是借助于開源的框架來實現,這就是幾乎每個項目都會引入的commons-io類庫。

          引入相應依賴:


          ?commons-io
          ?commons-io
          ?2.7

          注意,不同的版本需要不同的JDK支持,2.7需要Java 8及以上版本。

          commons-io對實現文件監(jiān)聽的實現位于org.apache.commons.io.monitor包下,基本使用流程如下:

          • 自定義文件監(jiān)聽類并繼承 FileAlterationListenerAdaptor 實現對文件與目錄的創(chuàng)建、修改、刪除事件的處理;
          • 自定義文件監(jiān)控類,通過指定目錄創(chuàng)建一個觀察者 FileAlterationObserver
          • 向監(jiān)視器添加文件系統觀察器,并添加文件監(jiān)聽器;
          • 調用并執(zhí)行。

          第一步:創(chuàng)建文件監(jiān)聽器。根據需要在不同的方法內實現對應的業(yè)務邏輯處理。

          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");
          ?}
          }

          第二步:封裝一個文件監(jiān)控的工具類,核心就是創(chuàng)建一個觀察者FileAlterationObserver,將文件路徑Path和監(jiān)聽器FileAlterationListener進行封裝,然后交給FileAlterationMonitor。

          public?class?FileMonitor?{

          ?private?FileAlterationMonitor?monitor;

          ?public?FileMonitor(long?interval)?{
          ??monitor?=?new?FileAlterationMonitor(interval);
          ?}

          ?/**
          ??*?給文件添加監(jiān)聽
          ??*
          ??*?@param?path?????文件路徑
          ??*?@param?listener?文件監(jiān)聽器
          ??*/
          ?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();

          ?}
          }

          第三步:調用并執(zhí)行:

          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();
          ?}
          }

          執(zhí)行程序,會發(fā)現每隔1秒輸入一次日志。當文件發(fā)生變更時,也會打印出對應的日志:

          onStart
          修改:/Users/zzs/temp/1.txt
          onStop
          onStart
          onStop

          當然,對應的監(jiān)聽時間間隔,可以通過在創(chuàng)建FileMonitor時進行修改。

          該方案中監(jiān)聽器本身會啟動一個線程定時處理。在每次運行時,都會先調用事件監(jiān)聽處理類的onStart方法,然后檢查是否有變動,并調用對應事件的方法;比如,onChange文件內容改變,檢查完后,再調用onStop方法,釋放當前線程占用的CPU資源,等待下次間隔時間到了被再次喚醒運行。

          監(jiān)聽器是基于文件目錄為根源的,也可以可以設置過濾器,來實現對應文件變動的監(jiān)聽。過濾器的設置可查看FileAlterationObserver的構造方法:

          public?FileAlterationObserver(String?directoryName,?FileFilter?fileFilter,?IOCase?caseSensitivity)?{
          ????this(new?File(directoryName),?fileFilter,?caseSensitivity);
          }

          小結

          至此,基于Java實現監(jiān)聽文件變化的三種方案便介紹完畢。經過上述分析及實例,大家已經看到,并沒有完美的解決方案,根據自己的業(yè)務情況及系統的容忍度可選擇最適合的方案。而且,在此基礎上可以新增一些其他的輔助措施,來避免具體方案中的不足之處。


          面試官:你天天用 Lombok,說說它什么原理?我竟然答不上來…

          2022-05-28

          JDK的一個Bug,監(jiān)聽文件變更要小心了

          2022-05-27

          終于有大佬把"計算機底層原理"全部總結出來了

          2022-05-25

          Java 性能優(yōu)化的 55 個細節(jié)(珍藏版)

          2022-05-16

          多業(yè)務模式下的交易鏈路探索與實踐

          2022-05-14




          如果你覺得這篇文章不錯,那么,下篇通常會更好。備注“公眾號”添加微信好友(微信號:zhuan2quan)

          ▲?按關注”程序新視界“,洞察技術內幕


          瀏覽 184
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无码一二三 | 97红桃视频 | 美国一级大黄 | 婷婷久久视频 | 在线视频免费电影亚洲色图 |