java實時監控文件行尾內容

今天講一下怎樣用Java實現實時的監控文件行尾的追加內容,相似Linux命令面試

tail -f

在以前的面試中遇到過一個問題,就是用Java實現tail功能,以前的作法是作一個定時任務每隔1秒去讀取一次文件,去判斷內容是否有追加,若是有則輸出新追加的內容,這個作法雖然能勉強實現功能,可是有點太low,今天採用另一種實現方式,基於事件通知。app

1.WatchService

首先介紹一下WatchService類,WatchService能夠監控某一個目錄下的文件的變更(新增,修改,刪除)並以事件的形式通知文件的變動,這裏咱們能夠實時的獲取到文件的修改事件,而後計算出追加的內容,Talk is cheap,Show me the code.測試

Listener
  • 簡單的接口,只有一個fire方法,當事件發生時處理事件。
public interface Listener {

    /**
     * 發生文件變更事件時的處理邏輯
     * 
     * @param event
     */
    void fire(FileChangeEvent event);
}
FileChangeListener
  • Listener接口的實現類,處理文件變動事件。
public class FileChangeListener implements Listener {

    /**
     * 保存路徑跟文件包裝類的映射
     */
    private final Map<String, FileWrapper> map = new ConcurrentHashMap<>();

    public void fire(FileChangeEvent event) {
        switch (event.getKind().name()) {
        case "ENTRY_MODIFY":
            // 文件修改事件
            modify(event.getPath());
            break;
        default:
            throw new UnsupportedOperationException(
                    String.format("The kind [%s] is unsupport.", event.getKind().name()));
        }
    }

    private void modify(Path path) {
        // 根據全路徑獲取包裝類對象
        FileWrapper wrapper = map.get(path.toString());
        if (wrapper == null) {
            wrapper = new FileWrapper(path.toFile());
            map.put(path.toString(), wrapper);
        }
        try {
            // 讀取追加的內容
            new ContentReader(wrapper).read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
FileWrapper
  • 文件包裝類,包含文件和當前讀取的行號
public class FileWrapper {

    /**
     * 當前文件讀取的行數
     */
    private int currentLine;

    /**
     * 監聽的文件
     */
    private final File file;

    public FileWrapper(File file) {
        this(file, 0);
    }

    public FileWrapper(File file, int currentLine) {
        this.file = file;
        this.currentLine = currentLine;
    }

    public int getCurrentLine() {
        return currentLine;
    }

    public void setCurrentLine(int currentLine) {
        this.currentLine = currentLine;
    }

    public File getFile() {
        return file;
    }

}
FileChangeEvent
  • 文件變動事件
public class FileChangeEvent {

    /**
     * 文件全路徑
     */
    private final Path path;
    /**
     * 事件類型
     */
    private final WatchEvent.Kind<?> kind;

    public FileChangeEvent(Path path, Kind<?> kind) {
        this.path = path;
        this.kind = kind;
    }

    public Path getPath() {
        return this.path;
    }

    public WatchEvent.Kind<?> getKind() {
        return this.kind;
    }

}
ContentReader
  • 內容讀取類
public class ContentReader {

    private final FileWrapper wrapper;

    public ContentReader(FileWrapper wrapper) {
        this.wrapper = wrapper;
    }

    public void read() throws FileNotFoundException, IOException {
        try (LineNumberReader lineReader = new LineNumberReader(new FileReader(wrapper.getFile()))) {
            List<String> contents = lineReader.lines().collect(Collectors.toList());
            if (contents.size() > wrapper.getCurrentLine()) {
                for (int i = wrapper.getCurrentLine(); i < contents.size(); i++) {
                    // 這裏只是簡單打印出新加的內容到控制檯
                    System.out.println(contents.get(i));
                }
            }
            // 保存當前讀取到的行數
            wrapper.setCurrentLine(contents.size());
        }
    }

}
DirectoryTargetMonitor
  • 目錄監視器,監控目錄下文件的變化
public class DirectoryTargetMonitor {

    private WatchService watchService;

    private final FileChangeListener listener;

    private final Path path;

    private volatile boolean start = false;

    public DirectoryTargetMonitor(final FileChangeListener listener, final String targetPath) {
        this(listener, targetPath, "");
    }

    public DirectoryTargetMonitor(final FileChangeListener listener, final String targetPath, final String... morePaths) {
        this.listener = listener;
        this.path = Paths.get(targetPath, morePaths);
    }

    public void startMonitor() throws IOException {
        this.watchService = FileSystems.getDefault().newWatchService();
        // 註冊變動事件到WatchService
        this.path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
        this.start = true;
        while (start) {
            WatchKey watchKey = null;
            try {
                // 阻塞直到有事件發生
                watchKey = watchService.take();
                watchKey.pollEvents().forEach(event -> {
                    WatchEvent.Kind<?> kind = event.kind();
                    Path path = (Path) event.context();
                    Path child = this.path.resolve(path);
                    listener.fire(new FileChangeEvent(child, kind));
                });
            } catch (Exception e) {
                this.start = false;
            } finally {
                if (watchKey != null) {
                    watchKey.reset();
                }
            }
        }
    }

    public void stopMonitor() throws IOException {
        System.out.printf("The directory [%s] monitor will be stop ...\n", path);
        Thread.currentThread().interrupt();
        this.start = false;
        this.watchService.close();
        System.out.printf("The directory [%s] monitor will be stop done.\n", path);
    }

}
測試類
  • 在D盤新建一個monitor文件夾, 新建一個test.txt文件,而後啓動程序,程序啓動完成後,咱們嘗試往test.txt添加內容而後保存,控制檯會實時的輸出咱們追加的內容,PS:追加的內容要以新起一行的形式追加,若是隻是在原來的尾行追加,本程序不會輸出到控制檯,有興趣的同窗能夠擴展一下
public static void main(String[] args) throws IOException {
        DirectoryTargetMonitor monitor = new DirectoryTargetMonitor(new FileChangeListener(), "D:\\monitor");
        monitor.startMonitor();
    }
相關文章
相關標籤/搜索