Is it possible to read from a InputStream with a timeout?

Specifically, the problem is to write a method like this:

int maybeRead(InputStream in, long timeout)

where the return value is the same as in.read() if data is available within 'timeout' milliseconds, and -2 otherwise. Before the method returns, any spawned threads must exit.

To avoid arguments, the subject here java.io.InputStream, as documented by Sun (any Java version). Please note this is not as simple as it looks. Below are some facts which are supported directly by Sun's documentation.

  • The in.read() method may be non-interruptible.

  • Wrapping the InputStream in a Reader or InterruptibleChannel doesn't help, because all those classes can do is call methods of the InputStream. If it were possible to use those classes, it would be possible to write a solution that just executes the same logic directly on the InputStream.

  • It is always acceptable for in.available() to return 0.

  • The in.close() method may block or do nothing.

  • There is no general way to kill another thread.


  • Using inputStream.available()

    It is always acceptable for System.in.available() to return 0.

    I've found the opposite - it always returns the best value for the number of bytes available. Javadoc for InputStream.available() :

    Returns an estimate of the number of bytes that can be read (or skipped over) 
    from this input stream without blocking by the next invocation of a method for 
    this input stream.
    

    An estimate is unavoidable due to timing/staleness. The figure can be a one-off underestimate because new data are constantly arriving. However it always "catches up" on the next call - it should account for all arrived data, bar that arriving just at the moment of the new call. Permanently returning 0 when there are data fails the condition above.

    First Caveat: Concrete subclasses of InputStream are responsible for available()

    InputStream is an abstract class. It has no data source. It's meaningless for it to have available data. Hence, javadoc for available() also states:

    The available method for class InputStream always returns 0.
    
    This method should be overridden by subclasses.
    

    And indeed, the concrete input stream classes do override available(), providing meaningful values, not constant 0s.

    Second Caveat: Ensure you use carriage-return when typing input in Windows.

    If using System.in , your program only receives input when your command shell hands it over. If you're using file redirection/pipes (eg somefile > java myJavaApp or somecommand | java myJavaApp ), then input data are usually handed over immediately. However, if you manually type input, then data handover can be delayed. Eg With windows cmd.exe shell, the data are buffered within cmd.exe shell. Data are only passed to the executing java program following carriage-return (control-m or <enter> ). That's a limitation of the execution environment. Of course, InputStream.available() will return 0 for as long as the shell buffers the data - that's correct behaviour; there are no available data at that point. As soon as the data are available from the shell, the method returns a value > 0. NB: Cygwin uses cmd.exe too.

    Simplest solution (no blocking, so no timeout required)

    Just use this:

        byte[] inputData = new byte[1024];
        int result = is.read(inputData, 0, is.available());  
        // result will indicate number of bytes read; -1 for EOF with no data read.
    

    OR equivalently,

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
        // ...
             // inside some iteration / processing logic:
             if (br.ready()) {
                 int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
             }
    

    Richer Solution (maximally fills buffer within timeout period)

    Declare this:

    public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
         throws IOException  {
         int bufferOffset = 0;
         long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
         while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
             int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
             // can alternatively use bufferedReader, guarded by isReady():
             int readResult = is.read(b, bufferOffset, readLength);
             if (readResult == -1) break;
             bufferOffset += readResult;
         }
         return bufferOffset;
     }
    

    Then use this:

        byte[] inputData = new byte[1024];
        int readCount = readInputStreamWithTimeout(System.in, inputData, 6000);  // 6 second timeout
        // readCount will indicate number of bytes read; -1 for EOF with no data read.
    

    Assuming your stream is not backed by a socket (so you can't use Socket.setSoTimeout() ), I think the standard way of solving this type of problem is to use a Future.

    Suppose I have the following executor and streams:

        ExecutorService executor = Executors.newFixedThreadPool(2);
        final PipedOutputStream outputStream = new PipedOutputStream();
        final PipedInputStream inputStream = new PipedInputStream(outputStream);
    

    I have writer that writes some data then waits for 5 seconds before writing the last piece of data and closing the stream:

        Runnable writeTask = new Runnable() {
            @Override
            public void run() {
                try {
                    outputStream.write(1);
                    outputStream.write(2);
                    Thread.sleep(5000);
                    outputStream.write(3);
                    outputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        executor.submit(writeTask);
    

    The normal way of reading this is as follows. The read will block indefinitely for data and so this completes in 5s:

        long start = currentTimeMillis();
        int readByte = 1;
        // Read data without timeout
        while (readByte >= 0) {
            readByte = inputStream.read();
            if (readByte >= 0)
                System.out.println("Read: " + readByte);
        }
        System.out.println("Complete in " + (currentTimeMillis() - start) + "ms");
    

    which outputs:

    Read: 1
    Read: 2
    Read: 3
    Complete in 5001ms
    

    If there was a more fundamental problem, like the writer not responding, the reader would block for ever. If I wrap the read in a future, I can then control the timeout as follows:

        int readByte = 1;
        // Read data with timeout
        Callable<Integer> readTask = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return inputStream.read();
            }
        };
        while (readByte >= 0) {
            Future<Integer> future = executor.submit(readTask);
            readByte = future.get(1000, TimeUnit.MILLISECONDS);
            if (readByte >= 0)
                System.out.println("Read: " + readByte);
        }
    

    which outputs:

    Read: 1
    Read: 2
    Exception in thread "main" java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
        at java.util.concurrent.FutureTask.get(FutureTask.java:91)
        at test.InputStreamWithTimeoutTest.main(InputStreamWithTimeoutTest.java:74)
    

    I can catch the TimeoutException and do whatever cleanup I want.


    I would question the problem statement rather than just accept it blindly. You only need timeouts from the console or over the network. If the latter you have Socket.setSoTimeout() and HttpURLConnection.setReadTimeout() which both do exactly what is required, as long as you set them up correctly when you construct/acquire them. Leaving it to an arbitrary point later in the application when all you have is the InputStream is poor design leading to a very awkward implementation.

    链接地址: http://www.djcxy.com/p/78372.html

    上一篇: 如何从Java读取文件夹中的所有文件?

    下一篇: 是否有可能从一个InputStream读取超时?