如何确定BufferedReader的确切状态?

我有一个BufferedReader (由new BufferedReader(new InputStreamReader(process.getInputStream())) )。 我对BufferedReader的概念很BufferedReader但正如我所看到的,它有三种状态:

  1. 一条线正在等待阅读; 调用bufferedReader.readLine会立即返回此字符串。
  2. 流是开放的,但没有等待读取的行; 调用bufferedReader.readLine将挂起线程,直到一行变为可用。
  3. 溪流关闭; 调用bufferedReader.readLine将返回null。

现在我想确定BufferedReader的状态,以便我可以确定是否可以安全地从中读取而不挂起我的应用程序。 基本过程(见上文)众所周知是不可靠的,因此可能已经挂起; 在这种情况下,我不希望我的主机应用程序挂起。 因此我正在实施一种超时。 我试图首先使用线程进行此操作,但它变得非常复杂。

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

所以我的问题是:如何在不实际调用readLine情况下确定我的BufferedReader readLine的这三种状态中的哪一种? 不幸的是我不能只是调用readLine来检查这个,因为它打开了我的应用程序。

我使用的是JDK 1.5版。

存在一些状态,其中一些数据可能在缓冲区中,但不一定足以填充一条线。 在这种情况下, ready()将返回true ,但调用readLine()将阻塞。

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

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

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

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

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

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

(1)重新访问使用线程的想法。 这不必复杂,正确完成,它会让您的代码相当优雅地逃避阻塞的I / O读取,例如:

 final BufferedReader reader = ... ExecutorService executor = // create an executor here, using the Executors factory class. Callable task = new Callable { public String call() throws IOException { return reader.readLine(); } }; Future 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,但它具有非阻塞语义。

您是否通过实验确认即使基础流位于文件末尾,ready()也将返回false? 因为我不希望断言是正确的(虽然我没有做过实验)。

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

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

通常,您必须使用多个线程实现此function。 有一些特殊情况,例如从套接字读取,其中底层流具有内置的超时工具。

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

 private static final ExecutorService worker = Executors.newSingleThreadExecutor(); private static class Timeout implements Callable { 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(); 

您可以围绕InputStream或InputStreamReader创建自己的包装器,这些包装器在逐字节级别上工作,ready()返回准确的值。

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

如果您只想要超时,那么其他方法可能更好。 如果你想要一个非阻塞的缓冲读卡器,这就是我将如何使用线程:(请注意我没有对此进行测试,至少需要添加一些exception处理)

 public class MyReader implements Runnable { private final BufferedReader reader; private ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); 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()方法区分它们。