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

}

以上是 java实时监控文件行尾内容的实现 的全部内容, 来源链接: utcz.com/z/346126.html

回到顶部