最原始的想法和实现
最容易想到的就是不断的读取一个文件,如果读取到文件结尾(EOF),那么sleep一下然后再次尝试。
事实上Apache Common IO里就有一个这样的实现
这种方法到优点是简单而且不需要任何操作系统或者文件系统的支持(跨平台是不会有任何问题),缺点就是如果sleep过长,那么tail -f的结果不是很及时,如果太短,可能浪费到cpu和io很多
改进的思路很自然,就像io那样,从最原始的cpu轮询到中断的思路。
就像中断需要更多的硬件支持一样,改进版的tail也需要更多操作系统和文件系统到支持。
网上的相关资料
- GNU coreutils里的实现,也是我们在Linux里用到到命令,其实我们经常用到的命令实现起来也是不容易到,tail.c有两千多行代码。
- JavaEye里INotify的介绍 ,主要是inotify-tools这个工具到介绍,使用这个工具可以实现shell脚本监视文件系统的变化
- Inotify的介绍 ,里面有简洁的示例代码,我后面用c实现的copy了这里到代码。
- http://jnotify.sourceforge.net/ jni实现的跨平台的文件系统监控库,支持win32/64 Linux 和 Mac os(我只测试了Linux,不过它有其它系统到动态链接库文件)
- Java 7的nio.2 ,里面提供了监控目录的api,不过它好像只支持目录的监控,如果要监控文件,需要遍历目录的事件
Linux下的C实现
主要参考了Inotify的介绍,GNU coreutils到代码太长了,没细看
核心代码就3行: fd = inotify_init();
wd = inotify_add_watch(fd, argv1, IN_MODIFY);
read(fd, buffer, 1024);
#include <stdio.h>
#include <stdlib.h>
#include <linux/unistd.h>
#include <linux/inotify.h>
#include <errno.h>
int main(int argc, char** argv) {
char buffer[1024];
int ch;
long curFilePointer;
int fp;
int fd;
int wd;
if(argc!=2){
printf("usage: tail file-path.\n");
return EXIT_FAILURE;
}
fp=fopen(argv[1],"r");
if(fp<0){
printf("can't open %s\n", argv[1]);
return EXIT_FAILURE;
}
fd = inotify_init();
if (fd < 0) {
printf("Fail to initialize inotify.\n");
return EXIT_FAILURE;
}
wd = inotify_add_watch(fd, argv[1], IN_MODIFY);
if (wd < 0) {
printf("Can't add watch for %s.\n", argv[1]);
return EXIT_FAILURE;
}
while(1){
while ((ch=fgetc(fp))!=EOF){
putchar(ch);
curFilePointer++;
}
read(fd, buffer, 1024);
}
return EXIT_SUCCESS;
}
JNotify测试
下载zip解压后有个jar包和本地的dll,比如linux就是libJnotify.so
package test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyException;
import net.contentobjects.jnotify.JNotifyListener;
public class JavaTail {
/**
* @param args
* @throws JNotifyException
* @throws InterruptedException
* @throws FileNotFoundException
*/
public static void main(String[] args) throws JNotifyException, IOException, InterruptedException {
if(args.length!=1){
System.err.println("Usage: test.JavaTail filePath!");
return;
}
int mask = JNotify.FILE_MODIFIED;
int watchID = JNotify.addWatch(args[0], mask, false, (JNotifyListener) new Listener(args[0]));
Thread.sleep(Long.MAX_VALUE);
}
}
class Listener implements JNotifyListener {
FileReader fr = null;
public Listener(String filePath) throws IOException{
fr=new FileReader(filePath);
int ch = 0;
while((ch=fr.read())!=-1){
System.out.print((char)ch);
}
}
public void fileRenamed(int wd, String rootPath, String oldName,
String newName) {
}
public void fileModified(int wd, String rootPath, String name) {
int ch = 0;
try {
while((ch=fr.read())!=-1){
System.out.print((char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void fileDeleted(int wd, String rootPath, String name) {
}
public void fileCreated(int wd, String rootPath, String name) {
}
}
Java7 NIO.2的实现
可以看到只能监控目录到事件,然后变历,判断是不是我们关注到某个文件。
package test;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
public class JavaTail {
/**
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length!=1){
System.err.println("Usage: test.JavaTail filePath!");
return;
}
File file=new File(args[0]);
FileReader fr=new FileReader(file);
int ch = 0;
while((ch=fr.read())!=-1){
System.out.print((char)ch);
}
File parent=file.getParentFile();
Path filePath=file.toPath();
Path dir=parent.toPath();
WatchService watcher = FileSystems.getDefault().newWatchService();
WatchKey key=dir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while(true){
key=watcher.take();
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind ==StandardWatchEventKinds.ENTRY_MODIFY){
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();
Path child = dir.resolve(filename);
if(child.equals(filePath)){
while((ch=fr.read())!=-1){
System.out.print((char)ch);
}
}
}
}
if(!key.reset()){
System.err.println("key.reset() return false");
break;
}
}
}
}