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

Java實(shí)現(xiàn)文件變化監(jiān)聽代碼實(shí)例

 更新時間:2024年01月27日 09:59:19   作者:魅Lemon  
這篇文章主要介紹了Java實(shí)現(xiàn)文件變化監(jiān)聽代碼實(shí)例,通過定時任務(wù),輪訓(xùn)查詢文件的最后修改時間,與上一次進(jìn)行對比,如果發(fā)生變化,則說明文件已經(jīng)修改,進(jìn)行重新加載或?qū)?yīng)的業(yè)務(wù)邏輯處理,需要的朋友可以參考下

一、前言

1、簡介

在平時的開發(fā)過程中,會有很多場景需要實(shí)時監(jiān)聽文件的變化,如下:

通過實(shí)時監(jiān)控 mysql 的 binlog 日志實(shí)現(xiàn)數(shù)據(jù)同步

修改配置文件后,希望系統(tǒng)可以實(shí)時感知

應(yīng)用系統(tǒng)將日志寫入文件中,日志監(jiān)控系統(tǒng)可以實(shí)時抓取日志,分析日志內(nèi)容并進(jìn)行報警

類似 ide 工具,可以實(shí)時感知管理的工程下的文件變更

2、三種方法介紹

定時任務(wù) + File#lastModified

WatchService

Apache Commons-IO

二、三種方法實(shí)現(xiàn)

1、定時任務(wù) + File#lastModified

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

對于文件低頻變動的場景,這種方案實(shí)現(xiàn)簡單,基本上可以滿足需求。但該方案如果用在文件目錄的變化上,缺點(diǎn)就有些明顯了,比如:操作頻繁,效率都損耗在遍歷、保存狀態(tài)、對比狀態(tài)上了,無法充分利用OS的功能。

public class FileWatchDemo {
    /**
     * 上次更新時間
     */
    public static long LAST_TIME = 0L;
    public static void main(String[] args) throws Exception {
        // 相對路徑代表這個功能相同的目錄下
        String fileName = "static/test.json";
        // 創(chuàng)建文件,僅為實(shí)例,實(shí)踐中由其他程序觸發(fā)文件的變更
        createFile(fileName);
        // 循環(huán)執(zhí)行
        while (true){
            long timestamp = readLastModified(fileName);
            if (timestamp != LAST_TIME) {
                System.out.println("文件已被更新:" + timestamp);
                LAST_TIME = timestamp;
                // 重新加載,文件內(nèi)容
            } else {
                System.out.println("文件未更新");
            }
            Thread.sleep(1000);
        }
    }
    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();
    }
}

同時該方案存在Bug:在Java8和9的某些版本下,lastModified方法返回時間戳并不是毫秒,而是秒,也就是說返回結(jié)果的后三位始終為0

2、WatchService

2.1 介紹

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

相對于方案一,實(shí)現(xiàn)起來簡單,效率高。不足的地方也很明顯,只能監(jiān)聽當(dāng)前目錄下的文件和目錄,不能監(jiān)視子目錄。另外對于jdk8之后版本來說,該方案已經(jīng)實(shí)現(xiàn)實(shí)時監(jiān)聽,不存在準(zhǔn)實(shí)時的問題

2.2 簡單示例

public class WatchServiceDemo {
    public static void main(String[] args) throws IOException {
        // 這里的監(jiān)聽必須是目錄
        Path path = Paths.get("static");
        // 創(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, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY},
                SensitivityWatchEventModifier.LOW);
        // 創(chuàng)建一個線程,等待目錄下的文件發(fā)生變化
        try {
            while (true) {
                // 獲取目錄的變化:
                // take()是一個阻塞方法,會等待監(jiān)視器發(fā)出的信號才返回。
                // 還可以使用watcher.poll()方法,非阻塞方法,會立即返回當(dāng)時監(jiān)視器中是否有信號。
                // 返回結(jié)果WatchKey,是一個單例對象,與前面的register方法返回的實(shí)例是同一個;
                WatchKey key = watcher.take();
                // 處理文件變化事件:
                // key.pollEvents()用于獲取文件變化事件,只能獲取一次,不能重復(fù)獲取,類似隊(duì)列的形式。
                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();
        }
    }
}

2.3 完整示例

創(chuàng)建FileWatchedListener接口

public interface FileWatchedListener {
    void onCreated(WatchEvent<Path> watchEvent);
    void onDeleted(WatchEvent<Path> watchEvent);
    void onModified(WatchEvent<Path> watchEvent);
    void onOverflowed(WatchEvent<Path> watchEvent);
}

創(chuàng)建FileWatchedAdapter 實(shí)現(xiàn)類,實(shí)現(xiàn)文件監(jiān)聽的方法

public class FileWatchedAdapter implements FileWatchedListener {
    @Override
    public void onCreated(WatchEvent<Path> watchEvent) {
        Path fileName = watchEvent.context();
        System.out.println(String.format("文件【%s】被創(chuàng)建,時間:%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());
    }
}

創(chuàng)建FileWatchedService 監(jiān)聽類,監(jiān)聽文件

public class FileWatchedService {
    private WatchService watchService;
    private FileWatchedListener listener;
    /**
     *
     * @param path 要監(jiān)聽的目錄,注意該 Path 只能是目錄,否則會報錯 java.nio.file.NotDirectoryException: 
     * @param listener 自定義的 listener,用來處理監(jiān)聽到的創(chuàng)建、修改、刪除事件
     * @throws IOException
     */
    public FileWatchedService(Path path, FileWatchedListener listener) throws IOException {
        watchService = FileSystems.getDefault().newWatchService();
        path.register(watchService,
                /// 監(jiān)聽文件創(chuàng)建事件
                StandardWatchEventKinds.ENTRY_CREATE,
                /// 監(jiān)聽文件刪除事件
                StandardWatchEventKinds.ENTRY_DELETE,
                /// 監(jiān)聽文件修改事件
                StandardWatchEventKinds.ENTRY_MODIFY);
        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 有兩個狀態(tài):
             * {@link sun.nio.fs.AbstractWatchKey.State.READY ready} 就緒狀態(tài):表示可以監(jiān)聽事件
             * {@link sun.nio.fs.AbstractWatchKey.State.SIGNALLED signalled} 有信息狀態(tài):表示已經(jīng)監(jiān)聽到事件,不可以接續(xù)監(jiān)聽事件
             * 每次處理完事件后,必須調(diào)用 reset 方法重置 watchKey 的狀態(tài)為 ready,否則 watchKey 無法繼續(xù)監(jiān)聽事件
             */
            if (!watchKey.reset()) {
                break;
            }
        }
    }
    public static void main(String[] args) {
        try {
            Path path = Paths.get("static");
            FileWatchedService fileWatchedService = new FileWatchedService(path, new FileWatchedAdapter());
            fileWatchedService.watch();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、Apache Commons-IO

3.1 介紹與環(huán)境準(zhǔn)備

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

  • 自定義文件監(jiān)聽類并繼承 FileAlterationListenerAdaptor 實(shí)現(xiàn)對文件與目錄的創(chuàng)建、修改、刪除事件的處理;
  • 自定義文件監(jiān)控類,通過指定目錄創(chuàng)建一個觀察者 FileAlterationObserver;
  • 向監(jiān)視器添加文件系統(tǒng)觀察器,并添加文件監(jiān)聽器;
  • 調(diào)用并執(zhí)行。
<!--注意,不同的版本需要不同的JDK支持,2.7需要Java 8及以上版本-->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

3.2 原理講解

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

監(jiān)聽器是基于文件目錄為根源的,也可以可以設(shè)置過濾器,來實(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);
}

3.3 實(shí)戰(zhàn)演示

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

public class FileListener extends FileAlterationListenerAdaptor {
    @Override
    public void onStart(FileAlterationObserver observer) {
        super.onStart(observer);
        // System.out.println("一輪輪詢開始,被監(jiān)視路徑:" + observer.getDirectory());
    }
    @Override
    public void onDirectoryCreate(File directory) {
        System.out.println("創(chuàng)建文件夾:" + 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("一輪輪詢結(jié)束,被監(jiān)視路徑:" + fileAlterationObserver.getDirectory());
    }
}

封裝一個文件監(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 {
        // 監(jiān)控間隔
        FileMonitor fileMonitor = new FileMonitor(10_000L);
        fileMonitor.monitor("static", new FileListener());
        fileMonitor.start();
    }
}

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

相關(guān)文章

最新評論