如何确定BufferedReader的确切状态?

时间:2010-01-23 18:03:42

标签: java io

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

  1. 一条线正在等待阅读;调用bufferedReader.readLine将立即返回此字符串。
  2. 流已打开,但没有等待读取的行;调用bufferedReader.readLine将挂起线程,直到一行可用。
  3. 溪流关闭;调用bufferedReader.readLine将返回null。
  4. 现在我想确定BufferedReader的状态,以便我可以确定是否可以安全地从中读取而不会挂起我的应用程序。基本过程(见上文)众所周知是不可靠的,因此可能已经挂起;在这种情况下,我不希望我的宿主应用程序挂起。因此我正在实施一种超时。我试图首先使用线程进行此操作,但它非常复杂。

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

    所以我的问题是:如何在不实际调用BufferedReader的情况下确定readLine所处的这三种状态中的哪一种?不幸的是,我不能只是致电readLine来检查这一点,因为它会让我的应用程序一直挂起。

    我使用的是JDK 1.5版。

8 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分: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();
    }
  }
}

答案 2 :(得分:1)

这是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,但它具有非阻塞语义。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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. 连接已关闭,无数据可用
  5. 您可以使用isClosed()和ready()方法区分它们。