How to determine the exact state of a BufferedReader?

后端 未结 8 1497
慢半拍i
慢半拍i 2020-12-17 00:21

I have a BufferedReader (generated by new BufferedReader(new InputStreamReader(process.getInputStream()))). I\'m quite new to the concept of a

相关标签:
8条回答
  • 2020-12-17 00:30

    There is a state where some data may be in the buffer, but not necessarily enough to fill a line. In this case, ready() would return true, but calling readLine() would block.

    You should easily be able to build your own ready() and readLine() methods. Your ready() would actually try to build up a line, and only when it has done so successfully would it return true. Then your readLine() could return the fully-formed line.

    0 讨论(0)
  • 2020-12-17 00:30

    Finally I found a solution to this. Most of the answers here rely on threads, but as I specified earlier, I am looking for a solution which doesn't require threads. However, my basis was the process. What I found was that processes seem to exit if both the output (called "input") and error streams are empty and closed. This makes sense if you think about it.

    So I just polled the output and error streams and also tried to determine if the process had exited or not. Below is a rough copy of my solution.

    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();
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-17 00:34

    In general, you have to implement this with multiple threads. There are special cases, like reading from a socket, where the underlying stream has a timeout facility built-in.

    However, it shouldn't be horribly complicated to do this with multiple threads. This is a pattern I use:

    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();
    
    0 讨论(0)
  • 2020-12-17 00:40

    If you just want the timeout then the other methods here are possibly better. If you want a non-blocking buffered reader, here's how I would do it, with threads: (please note I haven't tested this and at the very least it needs some exception handling added)

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

    Here's how to use it:

    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
    

    There are four possible states:

    1. Connection is open, no data is available
    2. Connection is open, data is available
    3. Connection is closed, data is available
    4. Connection is closed, no data is available

    You can distinguish between them with the isClosed() and ready() methods.

    0 讨论(0)
  • 2020-12-17 00:52

    You could use InputStream.available() to see if there is new output from the process. This should work the way you want it if the process outputs only full lines, but it's not really reliable.

    A more reliable approach to the problem would be to have a seperate thread dedicated to reading from the process and pushing every line it reads to some queue or consumer.

    0 讨论(0)
  • 2020-12-17 00:53

    You could make your own wrapper around InputStream or InputStreamReader that works on a byte-by-byte level, for which ready() returns accurate values.

    Your other options are threading which could be done simply (look into some of the concurrent data structures Java offers) and NIO, which is very complex and probably overkill.

    0 讨论(0)
提交回复
热议问题