如何使用Java读取正在被主动写入的文件?


99

我有一个将信息写入文件的应用程序。该信息在执行后用于确定应用程序的通过/失败/正确性。我希望能够在写入文件时读取它,以便可以实时进行通过/失败/正确性检查。

我认为可以做到这一点,但是使用Java时所涉及的陷阱是什么?如果阅读赶上了写作,它会只是等待更多的写操作直到文件关闭,还是此时读将引发异常?如果是后者,我该怎么办?

我的直觉目前将我推向BufferedStreams。这是要走的路吗?


1
嘿,因为我正面临类似的情况,如果您找到了比公认的解决方案更好的解决方案,我正在措辞?
阿萨夫·戴维

我知道这是一个古老的问题,但是为了将来的读者,您是否可以扩展用例?在没有更多信息的情况下,有人想知道您是否正在解决错误的问题。
user359996

查看使用Apache Commons IO 的Tailer。它处理大多数边缘情况。
2012年

2
使用数据库。这些“在写入文件时读取文件”的场景以眼泪结束。
2013年

@EJP-您推荐哪个数据库?我猜MySQL是一个好的开始?
2015年

Answers:


39

FileChannel.read(ByteBuffer)由于该示例不是阻塞读取,因此无法使用该示例。但是没有让下面的代码工作:

boolean running = true;
BufferedInputStream reader = new BufferedInputStream(new FileInputStream( "out.txt" ) );

public void run() {
    while( running ) {
        if( reader.available() > 0 ) {
            System.out.print( (char)reader.read() );
        }
        else {
            try {
                sleep( 500 );
            }
            catch( InterruptedException ex ) {
                running = false;
            }
        }
    }
}

当然,相同的东西可以用作计时器而不是线程,但是我将其留给程序员。我仍在寻找一种更好的方法,但这目前对我有效。

哦,我要警告一下:我正在使用1.4.2。是的,我知道我仍然处在石器时代。


1
感谢您添加...我从来没有做过的事情。我认为Blade锁定文件的答案也是不错的选择。但是,它需要Java 6(我认为)。
安东尼·克兰普

@JosephGordon-您将不得不转移到这些天之一的无人机时代;-)
TungstenX

12

如果要在写入文件时读取文件,并且仅读取新内容,则以下内容将帮助您实现相同目的。

要运行此程序,您将从命令提示符/终端窗口启动它,并将文件名传递给read。除非您终止该程序,否则它将读取文件。

java FileReader c:\ myfile.txt

键入文本行时,将其保存在记事本中,您将看到控制台中打印的文本。

public class FileReader {

    public static void main(String args[]) throws Exception {
        if(args.length>0){
            File file = new File(args[0]);
            System.out.println(file.getAbsolutePath());
            if(file.exists() && file.canRead()){
                long fileLength = file.length();
                readFile(file,0L);
                while(true){

                    if(fileLength<file.length()){
                        readFile(file,fileLength);
                        fileLength=file.length();
                    }
                }
            }
        }else{
            System.out.println("no file to read");
        }
    }

    public static void readFile(File file,Long fileLength) throws IOException {
        String line = null;

        BufferedReader in = new BufferedReader(new java.io.FileReader(file));
        in.skip(fileLength);
        while((line = in.readLine()) != null)
        {
            System.out.println(line);
        }
        in.close();
    }
}

2
在读取文件的时间和获取文件的长度之间,外部进程是否有可能向文件中添加更多数据。如果是这样,这将导致读取过程中丢失写入文件的数据。
JohnC

1
此代码消耗大量CPU,因为循环中没有thread.sleep调用。在不增加少量延迟的情况下,此代码往往会使CPU保持繁忙状态。
ChaitanyaBhatt

上面的两个评论都是正确的,此外BufferedReader,每次循环调用中的创建都是毫无意义的。创建它之后,就不必跳过,因为缓冲的读取器将在到达时读取新行。
Piotr


5

我完全同意约书亚的回应泰勒很适合这种情况。这是一个例子:

它每150毫秒在文件中写入一行,而每2500毫秒读取同一文件

public class TailerTest
{
    public static void main(String[] args)
    {
        File f = new File("/tmp/test.txt");
        MyListener listener = new MyListener();
        Tailer.create(f, listener, 2500);

        try
        {
            FileOutputStream fos = new FileOutputStream(f);
            int i = 0;
            while (i < 200)
            {
                fos.write(("test" + ++i + "\n").getBytes());
                Thread.sleep(150);
            }
            fos.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private static class MyListener extends TailerListenerAdapter
    {
        @Override
        public void handle(String line)
        {
            System.out.println(line);
        }
    }
}

您与Tailer的链接已断开。
Stealth Rabbi

2
@StealthRabbi然后最好的办法是搜索正确的链接,并用它编辑答案。
igracia '16

3

答案似乎是“否” ...和“是”。似乎没有真正的方法知道文件是否已打开以供其他应用程序写入。因此,从此类文件读取将一直进行到内容耗尽为止。我接受了Mike的建议,并编写了一些测试代码:

Writer.java将字符串写入文件,然后等待用户按下Enter键,然后再将另一行写入文件。其想法是可以启动它,然后可以启动阅读器以查看它如何处理“部分”文件。我写的读者在Reader.java中。

Writer.java

public class Writer extends Object
{
    Writer () {

    }

    public static String[] strings = 
        {
            "Hello World", 
            "Goodbye World"
        };

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

        java.io.PrintWriter pw =
            new java.io.PrintWriter(new java.io.FileOutputStream("out.txt"), true);

        for(String s : strings) {
            pw.println(s);
            System.in.read();
        }

        pw.close();
    }
}

Reader.java

public class Reader extends Object
{
    Reader () {

    }

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

        java.io.FileInputStream in = new java.io.FileInputStream("out.txt");

        java.nio.channels.FileChannel fc = in.getChannel();
        java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(10);

        while(fc.read(bb) >= 0) {
            bb.flip();
            while(bb.hasRemaining()) {
                System.out.println((char)bb.get());
            }
            bb.clear();
        }

        System.exit(0);
    }
}

不能保证此代码是最佳实践。

这使Mike建议您选择定期检查是否有新数据要从文件中读取的选项。然后,当确定读取已完成时,这需要用户干预以关闭文件读取器。或者,需要使读者知道文件的内容,并能够确定和结束写入条件。如果内容是XML,则可以使用文档末尾的信号。


1

不是Java本身,但是您可能会遇到这样的问题,即您已经向文件中写入了一些东西,但是尚未真正写入-它可能位于某个地方的缓存中,并且从同一文件中读取内容实际上可能不会给您新信息。

简短版本-使用flush()或任何相关的系统调用来确保您的数据实际上已写入文件中。

请注意,我不是在谈论操作系统级别的磁盘缓存-如果您的数据进入此处,则此后它应该出现在read()中。语言本身可能会缓存写操作,等待缓冲区填满或文件被刷新/关闭。


1

有一个开放源代码Java图形尾巴可以执行此操作。

https://stackoverflow.com/a/559146/1255493

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

1

您无法使用FileInputStream,FileReader或RandomAccessFile读取从另一个进程打开的文件。

但是直接使用FileChannel可以工作:

private static byte[] readSharedFile(File file) throws IOException {
    byte buffer[] = new byte[(int) file.length()];
    final FileChannel fc = FileChannel.open(file.toPath(), EnumSet.of(StandardOpenOption.READ));
    final ByteBuffer dst = ByteBuffer.wrap(buffer);
    fc.read(dst);
    fc.close();
    return buffer;
}

0

我从未尝试过,但是您应该编写一个测试用例,以查看结束后从流中读取是否可以工作,而不管是否有更多数据写入文件。

您有理由不能使用管道输入/输出流吗?是否正在从同一应用程序中写入和读取数据(如果有,您拥有数据,为什么需要从文件读取)?

否则,可能要读到文件末尾,然后监视更改并寻找到上次停止的地方并继续...尽管要注意比赛条件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.