【问题标题】:How to determine the exact state of a BufferedReader?如何确定 BufferedReader 的确切状态?
【发布时间】:2010-01-23 18:03:41
【问题描述】:

我有一个BufferedReader(由new BufferedReader(new InputStreamReader(process.getInputStream())) 生成)。我对BufferedReader 的概念很陌生,但在我看来,它具有三种状态:

  1. 有一行等待读取;调用bufferedReader.readLine 将立即返回此字符串。
  2. 流已打开,但没有等待读取的行;调用bufferedReader.readLine 将挂起线程,直到有一行可用。
  3. 流已关闭;调用 bufferedReader.readLine 将返回 null。

现在我想确定BufferedReader 的状态,以便确定是否可以在不挂起应用程序的情况下安全地读取它。底层过程(见上文)是出了名的不可靠,因此可能已经挂起;在这种情况下,我不希望我的主机应用程序挂起。因此,我正在实施一种超时。我试着先用线程来做这件事,但它变得非常复杂。

调用BufferedReader.ready() 不会区分上述情况(2)和(3)。换句话说,如果ready() 返回 false,则可能是流刚刚关闭(换句话说,我的底层进程正常关闭)或者底层进程挂起。

所以我的问题是:如何确定我的BufferedReader 处于这三个状态中的哪一个而不实际调用readLine?不幸的是,我不能只打电话给readLine 来检查这个,因为它会打开我的应用程序。

我使用的是 JDK 1.5 版。

【问题讨论】:

  • 顺便说一下,我暂时假设我的底层进程不能挂在一行中间......虽然这可能是一个错误的假设,但它没有发生到目前为止,在测试期间,我遇到了足够多的其他问题!
  • 注:我目前正在寻找除线程之外的其他选项。问题是我的应用目前无法运行(由于多种原因,包括 readLine),而且我发现线程很难调试,尤其是在它们超时的情况下。

标签: java io


【解决方案1】:

有些数据可能在缓冲区中,但不一定足以填满一行。在这种情况下,ready() 将返回 true,但调用 readLine() 会阻塞。

您应该能够轻松构建自己的 ready()readLine() 方法。您的ready() 实际上会尝试建立一条线,只有当它成功完成后才会返回true。然后你的readLine() 可以返回完整的行。

【讨论】:

  • 我相信这是最好的方法,除非需要额外的缓冲性能。
  • 这与您通过 ZoFrex 的解决方案有何不同?看起来我的自定义 ready() 必须在下面调用一些字节级的 ready(),这不能保证 read() 是否会阻塞。如果底层进程挂起,我的应用程序仍会打开可能会挂起。
  • 查看 Java 源代码,我发现 BufferedReader#ready() 必然是保守的,因为它检查自己的缓冲区和底层读取器,例如 InputStreamReader。 hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/… 我猜后者 InputStreamReader 也会保守地报告它的 ready() 状态。
【解决方案2】:

最后我找到了解决方案。这里的大多数答案都依赖于线程,但正如我之前指定的,我正在寻找一种不需要线程的解决方案。但是,我的基础是过程。我发现如果输出(称为“输入”)和错误流都为空且关闭,进程似乎会退出。如果您考虑一下,这是有道理的。

所以我只是轮询了输出和错误流,并尝试确定进程是否已退出。以下是我的解决方案的粗略副本。

public String readLineWithTimeout(Process process, long timeout) throws IOException, TimeoutException {
  BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
  BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  boolean finished = false;
  long startTime = 0;
  while (!finished) {
    if (output.ready()) {
      return output.readLine();
    } else if (error.ready()) {
      error.readLine();
    } else {
      try {
        process.exitValue();
        return null;
      } catch (IllegalThreadStateException ex) {
        //Expected behaviour
      }
    }
    if (startTime == 0) {
      startTime = System.currentTimeMills();
    } else if (System.currentTimeMillis() > startTime + timeout) {
      throw new TimeoutException();
    }
  }
}

【讨论】:

  • 我怀疑这可能仍然会阻塞,因为 Reader#ready() 表示单个字符(或更多)的可用性。我还怀疑可能存在所有读取器在 ready() 中报告错误但进程的管道有数据要读取的情况,可能会阻塞 read() 系统调用。
  • 我知道这已经得到解答。但是自从我两次遇到这种情况以来。提示:在尝试从正在执行的进程中读取之前,您需要等待。您可能刚刚开始阅读,而该过程尚未完全执行,这意味着 ready() 或 readLine() 可能会无缘无故地阻塞......
【解决方案3】:

这是 java 的阻塞 I/O API 的一个非常基本的问题。

我怀疑你会选择以下之一:

(1) 重新审视使​​用线程的想法。这不必很复杂,正确地完成,它会让你的代码非常优雅地避开阻塞的 I/O 读取,例如:

final BufferedReader reader = ...
ExecutorService executor = // create an executor here, using the Executors factory class.
Callable<String> task = new Callable<String> {
   public String call() throws IOException {
      return reader.readLine();
   }
};
Future<String> futureResult = executor.submit(task);
String line = futureResult.get(timeout);  // throws a TimeoutException if the read doesn't return in time

(2) 使用java.nio 代替java.io。这是一个更复杂的 API,但它具有非阻塞语义。

【讨论】:

    【解决方案4】:

    您是否通过实验证实了您的断言,即使底层流位于文件末尾,ready() 也会返回 false?因为我不希望这个断言是正确的(虽然我没有做过实验)。

    【讨论】:

    • 不幸的是,他是正确的。这是 Java IO 中最大的“陷阱”之一,这让我很难过 :(
    【解决方案5】:

    您可以使用 InputStream.available() 来查看进程是否有新的输出。如果进程只输出完整的行,这应该可以按照您想要的方式工作,但它并不真正可靠。

    解决该问题的一种更可靠的方法是有一个单独的线程专门用于从进程中读取数据并将它读取的每一行推送到某个队列或消费者。

    【讨论】:

    • InputStream.available() 在我的原始帖子中区分情况 (2) 和 (3) 是否比 ready() 更好?如果是,为什么说它不可靠?
    • Reader.ready() 最终使用 InputStream.available() 来判断是否有可用的数据。因此,它既不太可靠,也不太可靠。问题是即使有一些可用数据,也不能保证 InputStream.available() 会报告任何可用数据。这是因为流本身可能无法在不阻塞的情况下找出是否有可用的东西。即使它报告了某些内容,您也不能确定它是否会以换行符结束。如果它没有以换行符结束,在 ready()==true 上调用 readLine 仍然会阻塞。我建议使用专用线程。
    • 谢谢,在我看来,available() 并没有比 ready() 更好地区分情况 (2) 和 (3),所以这可能无法解决我的问题。
    【解决方案6】:

    通常,您必须使用多个线程来实现这一点。在某些特殊情况下,例如从套接字读取,底层流具有内置的超时功能。

    但是,使用多个线程执行此操作应该不会太复杂。这是我使用的模式:

    private static final ExecutorService worker = 
      Executors.newSingleThreadExecutor();
    
    private static class Timeout implements Callable<Void> {
      private final Closeable target;
      private Timeout(Closeable target) {
        this.target = target;
      }
      public Void call() throws Exception {
        target.close();
        return null;
      }
    }
    
    ...
    
    InputStream stream = process.getInputStream();
    Future<?> task = worker.schedule(new Timeout(stream), 5, TimeUnit.SECONDS);
    /* Use the stream as you wish. If it hangs for more than 5 seconds, 
       the underlying stream is closed, raising an IOException here. */
    ...
    /* If you get here without timing out, cancel the asynchronous timeout 
      and close the stream explicitly. */
    if(task.cancel(false))
      stream.close();
    

    【讨论】:

      【解决方案7】:

      您可以在 InputStream 或 InputStreamReader 周围制作自己的包装器,该包装器在逐字节级别上工作,ready() 会返回准确的值。

      您的其他选项是可以简单地完成的线程(查看 Java 提供的一些并发数据结构)和 NIO,这非常复杂并且可能矫枉过正。

      【讨论】:

      • 这听起来像是我最好的选择(与 lavinio 的回答类似),但是当你说“ready() 返回准确的值”时,ready() 如何区分我的情况 (2) 和 (3) ?
      • 顺便说一下,InputStream 不支持 ready() 方法,而 InputStreamReader 支持它,但是 javadoc 仍然说“注意返回 false 并不能保证下一次读取会阻塞。”
      • 如果它返回 true 但它确实保证下一次读取不会阻塞。我认为。我不知道如何区分情况2和3,只能建议测试一下!
      • 谢谢,但我的整个问题是关于区分情况 2 和 3。看起来线程是我唯一的选择......叹息......
      【解决方案8】:

      如果你只是想要超时,那么这里的其他方法可能会更好。如果你想要一个非阻塞缓冲阅读器,我会这样做,使用线程:(请注意我没有测试过这个,至少它需要添加一些异常处理)

      public class MyReader implements Runnable {
          private final BufferedReader reader;
          private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
          private boolean closed = false;
      
          public MyReader(BufferedReader reader) {
              this.reader = reader;
          }
      
          public void run() {
              String line;
              while((line = reader.readLine()) != null) {
                  queue.add(line);
              }
              closed = true;
          }
      
          // Returns true iff there is at least one line on the queue
          public boolean ready() {
              return(queue.peek() != null);
          }
      
          // Returns true if the underlying connection has closed
          // Note that there may still be data on the queue!
          public boolean isClosed() {
              return closed;
          }
      
          // Get next line
          // Returns null if there is none
          // Never blocks
          public String readLine() {
              return(queue.poll());
          }
      }
      

      使用方法如下:

      BufferedReader b; // Initialise however you normally do
      MyReader reader = new MyReader(b);
      new Thread(reader).start();
      
      // True if there is data to be read regardless of connection state
      reader.ready();
      
      // True if the connection is closed
      reader.closed();
      
      // Gets the next line, never blocks
      // Returns null if there is no data
      // This doesn't necessarily mean the connection is closed, it might be waiting!
      String line = reader.readLine(); // Gets the next line
      

      有四种可能的状态:

      1. 连接已打开,没有可用数据
      2. 连接已打开,数据可用
      3. 连接已关闭,数据可用
      4. 连接已关闭,没有可用数据

      您可以使用 isClosed() 和 ready() 方法区分它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-06
        • 2010-09-17
        • 2017-08-20
        • 2019-01-27
        • 1970-01-01
        • 2018-04-03
        相关资源
        最近更新 更多