在Java中,监控文件追加的最佳/最安全模式是什么?

14

有一个其他进程正在创建CSV文件,它会在事件发生时逐行追加。我对文件格式或其他进程没有控制权,但我知道它只会追加。

在Java程序中,我想监视此文件,并在添加一行时读取新行并根据内容做出反应。暂时忽略CSV解析问题。最好的方法是如何监视文件变化并逐行读取?

理想情况下,这将使用标准库类。该文件可能位于网络驱动器上,因此我希望能够提供一些强大的故障恢复功能。如果可能的话,我不想使用轮询 - 我更喜欢某种阻塞解决方案。

编辑-- 鉴于标准类无法提供阻止解决方案(谢谢答案),最稳健的轮询解决方案是什么?我不想每次都重新读取整个文件,因为文件可能会变得非常大。

7个回答

7
自Java 7以来,newWatchService()方法已经存在于FileSystem class上。
然而,有一些注意点:
  • 它仅适用于Java 7
  • 这是一种可选的方法
  • 它只监视目录,因此您必须自己处理文件,并关注文件的移动等问题
在Java 7之前,使用标准API是不可能的。
我尝试了以下方法(每秒轮询一次),它可以工作(只在处理中打印)。
  private static void monitorFile(File file) throws IOException {
    final int POLL_INTERVAL = 1000;
    FileReader reader = new FileReader(file);
    BufferedReader buffered = new BufferedReader(reader);
    try {
      while(true) {
        String line = buffered.readLine();
        if(line == null) {
          // end of file, start polling
          Thread.sleep(POLL_INTERVAL);
        } else {
          System.out.println(line);
        }
      }
    } catch(InterruptedException ex) {
     ex.printStackTrace();
    }
  }

由于没有人提出使用当前生产的Java的解决方案,因此我想添加它。如果有缺陷,请在评论中添加。


1
我的需求是监视一个文件夹,一旦有文件被添加/写入/移动到文件夹中,立即对其采取行动(例如将文件发送电子邮件)。我遇到的问题是,当文件很大时,可能需要一段时间才能完成写入或复制,而FILE_CREATE事件会在文件的第一个字节被写入文件夹时立即宣布。因此,我无法立即执行操作。有什么可靠的方法可以确定文件是否已完全写入,然后再对其执行任何操作? - Web User

5
您可以通过使用WatchService类来注册,如果文件发生任何更改,文件系统将通知您。这需要Java7,这是文档链接:http://docs.oracle.com/javase/tutorial/essential/io/notification.html 以下是代码片段:
public FileWatcher(Path dir) {
   this.watcher = FileSystems.getDefault().newWatchService();
   WatchKey key = dir.register(watcher, ENTRY_MODIFY);
}

void processEvents() {
    for (;;) {
        // wait for key to be signalled
        WatchKey key;
        try {
            key = watcher.take();
        } catch (InterruptedException x) {
            return;
        }

        for (WatchEvent<?> event : key.pollEvents()) {
            WatchEvent.Kind<?> kind = event.kind();

            if (kind == OVERFLOW) {
                continue;
            }
            // Context for directory entry event is the file name of entry
            WatchEvent<Path> ev = cast(event);
            Path name = ev.context();
            Path child = dir.resolve(name);
            // print out event
            System.out.format("%s: %s file \n", event.kind().name(), child);
        }
        // reset key and remove from set if directory no longer accessible
        boolean valid = key.reset();
    }
}

1
你能否编辑你的回答,说明这是Java 7中的新功能,它在java.nio中,并且newWatchService()是一个可选方法。也许可以添加一个指向javadoc的链接? - Nick Fortescue

3

使用标准库类无法实现此功能。有关详细信息,请参见此问题

为了进行有效的轮询,最好使用随机访问。如果您记住上一次文件结束的位置并从那里开始读取,它将会很有帮助。


谢谢 - 如我所编辑的问题所示,这意味着我需要一个调查解决方案。您有任何关于最稳健/高效的建议吗? - Nick Fortescue

3

使用Java 7的WatchService,它是NIO.2的一部分。

WatchService API专为需要接收文件变更事件通知的应用程序设计。


1
哇,Java 7 发布了吗?我一定是在洞里呆了很长时间了。 - kgiannakakis
目前有早期访问预览版发布或最新的二进制快照版本发布。 - Stephen Denne
2
WatchService 观察的是目录,而不是文件。 - finnw

2
只是为了补充Nick Fortescue上一篇文章的内容,下面是两个类,您可以同时运行它们(例如在两个不同的shell窗口中),这表明一个给定的文件可以同时被一个进程写入和另一个进程读取。
在这里,这两个进程将执行这些Java类,但我假设写入过程可以来自任何其他应用程序。(假设它没有对某些操作系统上的文件保持独占锁定-是否存在这样的文件系统锁定?)
我已经成功测试了这两个类在Windoze和Linux上。如果有某些条件(例如操作系统)失败,我非常想知道。
第一类:
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;

public class FileAppender {

    public static void main(String[] args) throws Exception {
        if ((args != null) && (args.length != 0)) throw
            new IllegalArgumentException("args is not null and is not empty");

        File file = new File("./file.txt");
        int numLines = 1000;
        writeLines(file, numLines);
    }

    private static void writeLines(File file, int numLines) throws Exception {
        PrintWriter pw = null;
        try {
            pw = new PrintWriter( new FileWriter(file), true );
            for (int i = 0; i < numLines; i++) {
                System.out.println("writing line number " + i);
                pw.println("line number " + i);
                Thread.sleep(100);
            }
        }
        finally {
            if (pw != null) pw.close();
        }
    }

}

第二课:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class FileMonitor {

    public static void main(String[] args) throws Exception {
        if ((args != null) && (args.length != 0)) throw
            new IllegalArgumentException("args is not null and is not empty");

        File file = new File("./file.txt");
        readLines(file);
    }

    private static void readLines(File file) throws Exception {
        BufferedReader br = null;
        try {
            br = new BufferedReader( new FileReader(file) );
            while (true) {
                String line = br.readLine();
                if (line == null) { // end of file, start polling
                    System.out.println("no file data available; sleeping..");
                    Thread.sleep(2 * 1000);
                }
                else {
                    System.out.println(line);
                }
            }
        }
        finally {
            if (br != null) br.close();
        }
    }

}

分别运行这两个对我来说是可以的,但如果我只运行FileMonitor并使用vim手动编辑file.txt,则不会识别更改。有什么想法吗? - Ed Mazur

2

0

轮询,可以按照固定周期或随机周期进行;200-2000毫秒应该是一个不错的随机轮询间隔范围。

检查两件事情...

如果您必须监视文件增长,则检查EOF /字节计数,并确保将其与fileAccess或fileWrite时间与上一次轮询进行比较。如果(>),则文件已被写入。

然后,结合检查独占锁/读取访问权限。如果文件可以被读取锁定并且它已经增长,则正在写入它的任何内容都已完成。

仅检查其中一个属性不一定会为您提供已编写++ 实际完成并可用于使用的保证状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接