unix/linux “tail -f” 的 Java IO 实现

Java IO implementation of unix/linux "tail -f"

提问人:Gary 提问时间:2/18/2009 最后编辑:ChirloGary 更新时间:12/2/2016 访问量:81609

问:

我想知道使用什么技术和/或库来实现 linux 命令“tail -f”的功能。我本质上是在寻找 .客户端代码可能如下所示:java.io.FileReader

TailFileReader lft = new TailFileReader("application.log");
BufferedReader br = new BufferedReader(lft);
String line;
try {
  while (true) {
    line= br.readLine();
    // do something interesting with line
  }
} catch (IOException e) {
  // barf
}

缺失的部分是合理的实现。它应该能够读取打开文件之前存在的文件部分以及添加的行。TailFileReader

java 文件-io iostream tail

评论


答:

43赞 matt b 2/18/2009 #1

能够继续读取文件,并等待文件有更多更新,这应该不难在代码中完成。下面是一些伪代码:

BufferedReader br = new BufferedReader(...);
String line;
while (keepReading) {
    line = reader.readLine();
    if (line == null) {
        //wait until there is more of the file for us to read
        Thread.sleep(1000);
    }
    else {
        //do something interesting with the line
    }
}

我假设您希望将这种类型的功能放在它自己的 Thread 中,以便您可以休眠它而不会影响应用程序的任何其他区域。您可能希望在 setter 中公开,以便您的主类/应用程序的其他部分可以安全地关闭线程,而不会出现任何其他麻烦,只需调用或类似的东西即可。keepReadingstopReading()

评论

5赞 Aaron Digulla 3/6/2009
注意:如果要尾部,请使用 br.skip (file.length ());我尝试了 RandomAccessReader(),但速度非常慢。
13赞 10/27/2010
这不考虑文件截断;如果日志文件被覆盖,则此代码将失败...这是 tail 的基本特征!
0赞 sheki 12/1/2010
这不会处理日志文件的滚动更新。
0赞 Sumit Kumar Saha 1/22/2020
这适用于我的用例,花了 2 个多小时才想出一个干净的解决方案
-1赞 Esko 2/18/2009 #2

这里有一个小故事,你可以用它来作为指针:

出于同样的原因,我在工作中编写了 TailingInputStream 代码。它基本上使用 File 并按需刷新其内容,如果它发生了重大变化(4kB 内存标记 IIRC),则根据内部缓冲区进行检查,然后执行尾部 -f 的作用。是的,有点骇人听闻,但它运行良好,不会惹恼 Threads 或类似的东西——它至少可以追溯到 1.4.2。

也就是说,它比 ReverseInputStream 容易得多,后者从文件的末尾到开始,如果文件动态更新,它就不会死......

13赞 aldrinleal 2/18/2009 #3

检查 JLogTailer,它执行此逻辑。

代码中的要点是:

public void run() {
    try {
        while (_running) {
            Thread.sleep(_updateInterval);
            long len = _file.length();
            if (len < _filePointer) {
                // Log must have been jibbled or deleted.
                this.appendMessage("Log file was reset. Restarting logging from start of file.");
                _filePointer = len;
            }
            else if (len > _filePointer) {
                // File must have had something added to it!
                RandomAccessFile raf = new RandomAccessFile(_file, "r");
                raf.seek(_filePointer);
                String line = null;
                while ((line = raf.readLine()) != null) {
                    this.appendLine(line);
                }
                _filePointer = raf.getFilePointer();
                raf.close();
            }
        }
    }
    catch (Exception e) {
        this.appendMessage("Fatal error reading log file, log tailing has stopped.");
    }
    // dispose();
}

评论

0赞 sheki 12/1/2010
JLogTailer 似乎没有库。
0赞 Karussell 3/30/2011
@sheki只用罐子?@aldrinleal我不想创造一个新的答案......刚刚将代码粘贴到此处。我更喜欢 matt 更简单(+更快?)的版本:)
0赞 Hakanai 4/26/2020
作为代码审查的一点,您尚未指定用于读取该行的编码,但您以某种方式假设您已经读取了字符串。
1赞 Daniel Werner 12/3/2010 #4

如果你的代码只需要在Unix系统上运行,你也许可以逃脱直接调用。tail -f

作为一个更复杂的替代方案,你可以看看 GNU tail 的实现,并将其移植到 Java 上。(不过,我不确定这是否会让你的代码成为衍生作品。

评论

1赞 8/16/2011
我不熟悉 Java 如何处理执行 shell 命令;鉴于它永远不会退出,这会导致 Java 应用程序挂起吗?tail -f
0赞 Makky 4/10/2012
不,它不会导致 Java 挂起。我已经编写了类似的应用程序,并将很快在 sourceforge 上发布
9赞 Alexander Azarov 12/5/2010 #5

我前段时间在 Scala 中构建了一个简短的“tail -f”实现:tailf。它还负责文件轮换,您可以定义自己的逻辑,当它到达 EOF 或发现文件已被重命名时该做什么。

您可以看一下并将其移植到 Java 上,因为实际上那里没有什么复杂的。几点说明:主文件是 Tail.scala,基本上它定义了 FollowingInputStream,它负责 EOF/rename 和 follow 方法,该方法包装成 .因此,一旦结束,就会创建一个来自一个和另一个元素的下一个请求。FollowingInputStreamSequenceInputStreamFollowingInputStreamSequenceInputStreamEnumerationFollowingInputStream

61赞 Chetan S 2/9/2011 #6

看看 Tailer 类的 Apache Commons 实现。它似乎也处理日志轮换。

评论

0赞 Karussell 3/30/2011
多谢!顺便说一句:如果 logrotation 正确完成('cp logfile oldfile; > logfile'),那么 matt 的解决方案应该仍然有效,因为文件引用不会丢失!
2赞 Joe Casadonte 10/5/2014
请注意:如果您只想从文件末尾,那么即使在 2.4 版本(撰写本文时最新版本)中,Tailer 也存在一些问题。请参见:issues.apache.org/jira/browse/...
1赞 ViPup 9/14/2012 #7

只是遇到了同样的问题 - 在这里找到了“最简单”的实现:Java Tail

*很棒的东西 * - 准备生产;)

我希望代码引用不会丢弃一些许可证。

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;

    /**
     * Java implementation of the Unix tail command
     * 
     * @param args[0] File name
     * @param args[1] Update time (seconds). Optional. Default value is 1 second
     * 
     * @author Luigi Viggiano (original author) http://it.newinstance.it/2005/11/19/listening-changes-on-a-text-file-unix-tail-implementation-with-java/
     * @author Alessandro Melandri (modified by)
     * */
    public class Tail {

      static long sleepTime = 1000;

      public static void main(String[] args) throws IOException {

        if (args.length > 0){

          if (args.length > 1)
        sleepTime = Long.parseLong(args[1]) * 1000;

          BufferedReader input = new BufferedReader(new FileReader(args[0]));
          String currentLine = null;

          while (true) {

        if ((currentLine = input.readLine()) != null) {
          System.out.println(currentLine);
          continue;
        }

        try {
          Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          break;
        }

          }
          input.close();

        } else {
          System.out.println("Missing parameter!\nUsage: java JavaTail fileName [updateTime (Seconds. default to 1 second)]");
        }
      }
    }
5赞 cheffe 7/3/2015 #8

我最近偶然发现了 rxjava-file,它是 RxJava 的扩展。与其他解决方案相比,它利用了 Java 的 NIO。

import rx.Observable;
import rx.functions.Action1;
import com.github.davidmoten.rx.FileObservable;

// ... class definition omitted

public void tailLogFile() throws InterruptedException {
    Observable<String> tailer = FileObservable.tailer()
                                .file("application.log") // absolute path
                                .tailText();

    tailer.subscribe(
        new Action1<String>() {
            @Override
            public void call(String line) {
                System.out.println("you got line: " + line);
            }
        },
        new Action1<Throwable>() {
            @Override
            public void call(Throwable e) {
                System.out.println("you got error: " + e);
                e.printStackTrace();
            }
        }
    );

// this solution operates threaded, so something  
// is required that prevents premature termination

    Thread.sleep(120000);
}

评论

0赞 PlexQ 3/31/2016
对我来说,订阅调用似乎无限期地阻止,永远不会返回?
0赞 cheffe 3/31/2016
你刚才复制粘贴@PlexQ?你会把你的代码要点吗?
1赞 Mahesh K 10/26/2016 #9

我发现了这个不错的尾部实现。

作者 : amelandri

Souce 自: https://gist.github.com/amelandri/1376896

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * Java implementation of the Unix tail command
 * 
 * @param args[0] File name
 * @param args[1] Update time (seconds). Optional. Default value is 1 second
 * 
 * @author Luigi Viggiano (original author) http://it.newinstance.it/2005/11/19/listening-changes-on-a-text-file-unix-tail-implementation-with-java/
 * @author Alessandro Melandri (modified by)
 * */
public class Tail {

  static long sleepTime = 1000;

  public static void main(String[] args) throws IOException {

    if (args.length > 0){

      if (args.length > 1)
        sleepTime = Long.parseLong(args[1]) * 1000;

      BufferedReader input = new BufferedReader(new FileReader(args[0]));
      String currentLine = null;

      while (true) {

        if ((currentLine = input.readLine()) != null) {
          System.out.println(currentLine);
          continue;
        }

        try {
          Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          break;
        }

      }
      input.close();

    } else {
      System.out.println("Missing parameter!\nUsage: java JavaTail fileName [updateTime (Seconds. default to 1 second)]");
        }
      }

}