欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實現(xiàn)監(jiān)聽文件變化的三種方案詳解

 更新時間:2022年05月30日 10:34:26   作者:程序新視界  
這篇文章主要介紹了Java實現(xiàn)監(jiān)聽文件變化的三種方法,每種方案給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

背景

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

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

方案一:定時任務(wù) + File#lastModified

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

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

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

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 < 2; i++) {
   long timestamp = readLastModified(fileName);
   if (timestamp != LAST_TIME) {
    System.out.println("文件已被更新:" + timestamp);
    LAST_TIME = timestamp;
    // 重新加載,文件內(nèi)容
   } 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();
 }
}

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

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

方案二:WatchService

在Java 7中新增了java.nio.file.WatchService,通過它可以實現(xiàn)文件變動的監(jiān)聽。WatchService是基于操作系統(tǒng)的文件系統(tǒng)監(jiān)控器,可以監(jiān)控系統(tǒng)所有文件的變化,無需遍歷、無需比較,是一種基于信號收發(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,它是對操作系統(tǒng)的文件監(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()方法,非阻塞方法,會立即返回當(dāng)時監(jiān)視器中是否有信號。
        // 返回結(jié)果WatchKey,是一個單例對象,與前面的register方法返回的實例是同一個;
        WatchKey key = watcher.take();
        // 處理文件變化事件:
        // key.pollEvents()用于獲取文件變化事件,只能獲取一次,不能重復(fù)獲取,類似隊列的形式。
        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);
        }
        // 每次調(diào)用WatchService的take()或poll()方法時需要通過本方法重置
        if (!key.reset()) {
          break;
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
復(fù)制代碼

上述demo展示了WatchService的基本使用方式,注解部分也說明了每個API的具體作用。

通過WatchService監(jiān)聽文件的類型也變得更加豐富:

  • ENTRY_CREATE 目標(biāo)被創(chuàng)建
  • ENTRY_DELETE 目標(biāo)被刪除
  • ENTRY_MODIFY 目標(biāo)被修改
  • OVERFLOW 一個特殊的Event,表示Event被放棄或者丟失

如果查看WatchService實現(xiàn)類(PollingWatchService)的源碼,會發(fā)現(xiàn),本質(zhì)上就是開啟了一個獨立的線程來監(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;
 ? ? ? ? ? ? ? ? }});
 ?  }

也就是說,本來需要我們手動實現(xiàn)的部分,也由WatchService內(nèi)部幫我們完成了。

如果你編寫一個demo,進(jìn)行驗證時,會很明顯的感覺到WatchService監(jiān)控文件的變化并不是實時的,有時候要等幾秒才監(jiān)聽到文件的變化。以實現(xiàn)類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);
 ? ? ? ? ?  }
 ? ? ?  }

也就是說監(jiān)聽器由按照固定時間間隔的調(diào)度器來控制的,而這個時間間隔在SensitivityWatchEventModifier類中定義:

public enum SensitivityWatchEventModifier implements Modifier {
 ?  HIGH(2),
 ?  MEDIUM(10),
 ?  LOW(30);
    // ...
}

該類提供了3個級別的時間間隔,分別為2秒、10秒、30秒,默認(rèn)值為10秒。這個時間間隔可以在path#register時進(jìn)行傳遞:

path.register(watcher, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY},
        SensitivityWatchEventModifier.HIGH);

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

該API在Stack Overflow上也有人提出Java 7在Mac OS下有延遲的問題,甚至涉及到Windows和Linux系統(tǒng),筆者沒有進(jìn)行其他操作系統(tǒng)的驗證,如果你遇到類似的問題,可參考對應(yīng)的文章,尋求解決方案:http://www.dbjr.com.cn/article/249820.htm

方案三:Apache Commons-IO

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

引入相應(yīng)依賴:

<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.7</version>
</dependency>

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

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

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

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

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 讀取或重新加載文件內(nèi)容
      System.out.println("文件變更,進(jìn)行處理");
    }
  }
?
  @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進(jìn)行封裝,然后交給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();
?
  }
}

第三步:調(diào)用并執(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ā)現(xiàn)每隔1秒輸入一次日志。當(dāng)文件發(fā)生變更時,也會打印出對應(yīng)的日志:

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

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

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

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

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

小結(jié)

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

到此這篇關(guān)于Java實現(xiàn)監(jiān)聽文件變化的三種方法的文章就介紹到這了,更多相關(guān)Java監(jiān)聽文件變化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析

    Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析

    多態(tài)是指不同類的對象在調(diào)用同一個方法是所呈現(xiàn)出的多種不同行為,下面這篇文章主要給大家介紹了關(guān)于Java多態(tài)中向上轉(zhuǎn)型與向下轉(zhuǎn)型的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • SpringCloud turbine監(jiān)控實現(xiàn)過程解析

    SpringCloud turbine監(jiān)控實現(xiàn)過程解析

    這篇文章主要介紹了SpringCloud turbine監(jiān)控實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • springboot封裝響應(yīng)實體的實例代碼

    springboot封裝響應(yīng)實體的實例代碼

    這篇文章主要介紹了springboot封裝響應(yīng)實體,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • java注釋轉(zhuǎn)json插件開發(fā)實戰(zhàn)詳解

    java注釋轉(zhuǎn)json插件開發(fā)實戰(zhàn)詳解

    這篇文章主要為大家介紹了java注釋轉(zhuǎn)json插件開發(fā)實戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Hadoop組件簡介

    Hadoop組件簡介

    Hadoop作為一種分布式基礎(chǔ)架構(gòu),可以使用戶在不了解分布式底層細(xì)節(jié)的情況下,開發(fā)分布式程序。接下來通過本文給大家分享Hadoop組件簡介,感興趣的朋友一起看看吧
    2017-09-09
  • 最詳細(xì)的文件上傳下載實例詳解(推薦)

    最詳細(xì)的文件上傳下載實例詳解(推薦)

    在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳和下載功能是非常常用的功能,今天來講一下JavaWeb中的文件上傳和下載功能的實現(xiàn)。非常不錯,具有參考借鑒價值,感興趣的朋友一起看下吧
    2016-07-07
  • SpringCloud實戰(zhàn)之Zuul網(wǎng)關(guān)服務(wù)

    SpringCloud實戰(zhàn)之Zuul網(wǎng)關(guān)服務(wù)

    服務(wù)網(wǎng)關(guān)是分布式架構(gòu)中不可缺少的組成部分,是外部網(wǎng)絡(luò)和內(nèi)部服務(wù)之間的屏障。這篇文章主要介紹了SpringCloud實戰(zhàn)之Zuul網(wǎng)關(guān)服務(wù)。一起跟隨小編過來看看吧
    2018-05-05
  • Java使用遞歸復(fù)制文件夾及文件夾

    Java使用遞歸復(fù)制文件夾及文件夾

    這篇文章主要介紹了Java使用遞歸復(fù)制文件夾及文件夾,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • Java8?Stream?collect(Collectors.toMap())的使用

    Java8?Stream?collect(Collectors.toMap())的使用

    這篇文章主要介紹了Java8?Stream?collect(Collectors.toMap())的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 解決idea每次新建項目都需要重新指定maven目錄

    解決idea每次新建項目都需要重新指定maven目錄

    這篇文章主要介紹了解決idea每次新建項目都需要配置maven,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評論