java实时监控文件行尾内容

今天讲一下怎样用Java实现实时的监控文件行尾的追加内容,类似Linux命令

tail -f

在之前的面试中遇到过一个问题,就是用Java实现tail功能,之前的做法是做一个定时任务每隔1秒去读取一次文件,去判断内容是否有追加,如果有则输出新追加的内容,这个做法虽然能勉强实现功能,但是有点太low,今天采用另外一种实现方式,基于事件通知。

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

你可能感兴趣的:(文件读取,file)