【问题标题】:How do I use Java to read from a file that is actively being written to?如何使用 Java 从正在写入的文件中读取?
【发布时间】:2010-09-05 11:19:28
【问题描述】:

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

我认为可以做到这一点,但是在使用 Java 时有哪些问题?如果读取赶上写入,它会等待更多写入直到文件关闭,还是读取会在此时抛出异常?如果是后者,那我该怎么办?

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

【问题讨论】:

  • 嘿,因为我面临着类似的情况,如果您找到了比公认的解决方案更好的解决方案?
  • 我知道这是一个老问题,但为了未来的读者,您能否进一步扩展您的用例?在没有更多信息的情况下,人们想知道您是否解决了错误的问题。
  • 研究使用来自 Apache Commons IO 的 Tailer。它可以处理大多数边缘情况。
  • 使用数据库。这些“边写边读”的场景以泪水告终。
  • @EJP - 你推荐哪个数据库?我猜 MySQL 是一个好的开始?

标签: java file file-io


【解决方案1】:

无法使用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。是的,我知道我还处于石器时代。

【讨论】:

  • 感谢您添加此内容……这是我一直没有时间做的事情。我认为 Blade 锁定文件的答案也是一个不错的答案。但是,它需要 Java 6(我认为)。
  • @JosephGordon - 在这些日子里,你将不得不进入无人机时代;-)
【解决方案2】:

如果您想在写入文件时读取文件并且只读取新内容,那么以下内容将帮助您实现同样的目的。

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

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

【讨论】:

  • 是不是有可能在文件被读取到文件长度被占用之间,外部进程可以向文件添加更多数据。如果是这样,这将导致读取过程丢失写入文件的数据。
  • 这段代码消耗了大量的 CPU,因为循环中没有 thread.sleep 调用。在不增加少量延迟的情况下,此代码往往会使 CPU 非常繁忙。
  • 以上两个 cmets 都是正确的,此外,每个循环调用中的 BufferedReader 创建是如此毫无意义。一旦创建它就不需要跳过,因为缓冲的阅读器会在新行到达时读取它们。
【解决方案3】:

您还可以查看用于锁定文件的一部分的 java 通道。

http://java.sun.com/javase/6/docs/api/java/nio/channels/FileChannel.html

FileChannel 的这个功能可能是个开始

lock(long position, long size, boolean shared) 

此方法的调用将阻塞,直到该区域可以被锁定

【讨论】:

    【解决方案4】:

    我完全同意Joshua's responseTailer 适合这种情况。这是一个例子:

    它每 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 的链接已损坏。
    • @StealthRabbi 最好的办法是搜索正确的链接,并用它编辑答案。
    【解决方案5】:

    答案似乎是“不”……和“是”。似乎没有真正的方法可以知道文件是否已打开以供另一个应用程序写入。因此,从这样的文件中读取将一直进行,直到内容用尽。我采纳了 Mike 的建议并编写了一些测试代码:

    Writer.java 将一个字符串写入文件,然后等待用户敲击回车,然后再将另一行写入文件。这个想法是可以启动它,然后可以启动阅读器以查看它如何处理“部分”文件。我写的阅读器在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,则可以使用文档结尾来表示这一点。

    【讨论】:

      【解决方案6】:

      有一个开源 Java Graphic Tail 可以做到这一点。

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

      【讨论】:

        【解决方案7】:

        您无法读取使用 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;
        }
        

        【讨论】:

          【解决方案8】:

          本身不是 Java,但您可能会遇到问题:您已将某些内容写入文件,但尚未真正写入 - 它可能位于某个缓存中,而从同一个文件中读取可能不会实际上给你新的信息。

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

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

          【讨论】:

            【解决方案9】:

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

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

            否则,可能会一直读取到文件末尾,然后监视更改并寻找您离开的地方并继续……但要注意竞争条件。

            【讨论】:

              猜你喜欢
              • 2011-12-18
              • 2011-06-09
              • 1970-01-01
              • 2022-08-18
              • 1970-01-01
              • 2015-04-10
              • 1970-01-01
              • 1970-01-01
              • 2012-12-25
              相关资源
              最近更新 更多