Java 是否可以在超时的情况下读取InputStream?

Java 是否可以在超时的情况下读取InputStream?,java,timeout,inputstream,Java,Timeout,Inputstream,具体来说,问题在于编写一个如下方法: int maybeRead(InputStream in, long timeout) public class SafeBufferedReader extends BufferedReader{ private long millisTimeout; ( . . . ) @Override public int read(char[] cbuf, int off, int len) throws IOExcepti

具体来说,问题在于编写一个如下方法:

int maybeRead(InputStream in, long timeout)
public class SafeBufferedReader extends BufferedReader{

    private long millisTimeout;

    ( . . . )

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        try {
            waitReady();
        } catch(IllegalThreadStateException e) {
            return 0;
        }
        return super.read(cbuf, off, len);
    }

    protected void waitReady() throws IllegalThreadStateException, IOException {
        if(ready()) return;
        long timeout = System.currentTimeMillis() + millisTimeout;
        while(System.currentTimeMillis() < timeout) {
            if(ready()) return;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break; // Should restore flag
            }
        }
        if(ready()) return; // Just in case.
        throw new IllegalThreadStateException("Read timed out");
    }
}
其中,如果数据在“超时”毫秒内可用,则返回值与.read()中的相同,否则返回值为-2。在方法返回之前,必须退出所有派生的线程

为了避免参数,这里的主题是java.io.InputStream,如Sun(任何java版本)所述。请注意,这并不像看上去那么简单。以下是Sun文档直接支持的一些事实

  • in.read()方法可能是不可中断的

  • 在读取器或InterruptableChannel中包装InputStream没有帮助,因为这些类所能做的就是调用InputStream的方法。如果可以使用这些类,那么就可以编写一个直接在InputStream上执行相同逻辑的解决方案

  • in.available()返回0总是可以接受的

  • in.close()方法可能会阻塞或不执行任何操作

  • 没有杀死另一个线程的通用方法


  • 我还没有使用JavaNIO包中的类,但它们似乎在这里有一些帮助。具体来说,正如jt所说,NIO是最好(也是正确的)解决方案。如果你真的被输入流困住了,你可以

  • 生成一个线程,该线程的唯一任务是从InputStream读取结果,并将结果放入一个缓冲区,该缓冲区可以从原始线程读取,而无需阻塞。如果您只有一个流实例,那么这应该可以很好地工作。否则,您可以使用thread类中不推荐使用的方法终止线程,尽管这可能会导致资源泄漏

  • 依赖isAvailable来指示可以无阻塞地读取的数据。但是,在某些情况下(例如使用套接字),可能需要潜在的阻塞读取才能使isAvailable报告0以外的内容


  • 下面是一种从System.in获取NIO FileChannel并使用超时检查数据可用性的方法,这是问题中描述的问题的特例。在控制台上运行它,不要键入任何输入,然后等待结果。它在Windows和Linux上的Java6下测试成功

    import java.io.FileInputStream;
    import java.io.FilterInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.nio.ByteBuffer;
    import java.nio.channels.ClosedByInterruptException;
    
    public class Main {
    
        static final ByteBuffer buf = ByteBuffer.allocate(4096);
    
        public static void main(String[] args) {
    
            long timeout = 1000 * 5;
    
            try {
                InputStream in = extract(System.in);
                if (! (in instanceof FileInputStream))
                    throw new RuntimeException(
                            "Could not extract a FileInputStream from STDIN.");
    
                try {
                    int ret = maybeAvailable((FileInputStream)in, timeout);
                    System.out.println(
                            Integer.toString(ret) + " bytes were read.");
    
                } finally {
                    in.close();
                }
    
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    
        }
    
        /* unravels all layers of FilterInputStream wrappers to get to the
         * core InputStream
         */
        public static InputStream extract(InputStream in)
                throws NoSuchFieldException, IllegalAccessException {
    
            Field f = FilterInputStream.class.getDeclaredField("in");
            f.setAccessible(true);
    
            while( in instanceof FilterInputStream )
                in = (InputStream)f.get((FilterInputStream)in);
    
            return in;
        }
    
        /* Returns the number of bytes which could be read from the stream,
         * timing out after the specified number of milliseconds.
         * Returns 0 on timeout (because no bytes could be read)
         * and -1 for end of stream.
         */
        public static int maybeAvailable(final FileInputStream in, long timeout)
                throws IOException, InterruptedException {
    
            final int[] dataReady = {0};
            final IOException[] maybeException = {null};
            final Thread reader = new Thread() {
                public void run() {                
                    try {
                        dataReady[0] = in.getChannel().read(buf);
                    } catch (ClosedByInterruptException e) {
                        System.err.println("Reader interrupted.");
                    } catch (IOException e) {
                        maybeException[0] = e;
                    }
                }
            };
    
            Thread interruptor = new Thread() {
                public void run() {
                    reader.interrupt();
                }
            };
    
            reader.start();
            for(;;) {
    
                reader.join(timeout);
                if (!reader.isAlive())
                    break;
    
                interruptor.start();
                interruptor.join(1000);
                reader.join(1000);
                if (!reader.isAlive())
                    break;
    
                System.err.println("We're hung");
                System.exit(1);
            }
    
            if ( maybeException[0] != null )
                throw maybeException[0];
    
            return dataReady[0];
        }
    }
    

    有趣的是,当在NetBeans 6.5内部而不是在控制台上运行程序时,超时根本不起作用,调用System.exit()实际上是杀死僵尸线程所必需的。发生的情况是中断程序线程在调用reader.interrupt()时阻塞(!)。另一个测试程序(此处未显示)另外尝试关闭通道,但也不起作用。

    如果您的InputStream由套接字支持,则可以使用设置套接字超时(以毫秒为单位)。如果read()调用没有在指定的超时内解除阻塞,它将抛出SocketTimeoutException


    在进行read()调用之前,请确保在套接字上调用setSoTimeout。

    我会质疑问题陈述,而不是盲目地接受它。您只需要从控制台或通过网络超时。如果是后者,则有
    Socket.setSoTimeout()
    HttpURLConnection.setReadTimeout()
    ,只要在构造/获取它们时正确设置它们,它们都能完成所需的任务。在应用程序中,当您只有InputStream时,将其留待以后的任意点,这是一个糟糕的设计,导致了一个非常笨拙的实现。

    假设您的流没有套接字支持(因此您不能使用
    socket.setSocket()
    ),我认为解决这类问题的标准方法是使用未来

    假设我有以下执行器和流:

        ExecutorService executor = Executors.newFixedThreadPool(2);
        final PipedOutputStream outputStream = new PipedOutputStream();
        final PipedInputStream inputStream = new PipedInputStream(outputStream);
    
    我有一个writer,它写入一些数据,然后在写入最后一段数据并关闭流之前等待5秒钟:

        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);
    
    通常的阅读方法如下。读取将无限期地阻塞数据,因此这将在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");
    
    哪些产出:

    Read: 1
    Read: 2
    Read: 3
    Complete in 5001ms
    
    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)
    
    如果有一个更根本的问题,比如作者没有回应,读者会永远封锁。 如果我在将来包装读取,那么我可以按如下方式控制超时:

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

    我可以捕获TimeoutException并执行我想要的任何清理。

    使用inputStream.available()

    System.in.available()返回0总是可以接受的

    我发现了相反的情况——它总是返回可用字节数的最佳值。Javadoc for
    InputStream.available()

    由于时间/陈旧性,估算是不可避免的。这一数字可能是一次性低估,因为新数据不断出现。然而,它总是在下一次呼叫时“赶上”——它应该考虑所有到达的数据,除非是在新呼叫的时刻到达的数据。有数据时永久返回0不符合上述条件

    第一个警告:InputStream的具体子类负责提供()

    InputStream
    是一个抽象类。它没有数据源。对它来说,有可用的数据是毫无意义的。因此,javadoc for
    available()
    还声明:

    The available method for class InputStream always returns 0.
    
    This method should be overridden by subclasses.
    
    实际上,具体的输入流类确实覆盖了available(),提供了有意义的值,而不是常量0

    第二个警告:确保在Windows中键入输入时使用回车符。

    如果在中使用
    System.in,则程序仅在命令shell移交输入时接收输入。如果您正在使用文件重定向/管道(例如somefile>javamyjavaapp或somecommand | javamyjavaapp),则通常会立即移交输入数据。但是,如果手动输入,则数据移交可能会延迟。例如,对于windows cmd.exe shell,数据缓冲在cmd.exe shell中。数据仅在回车后传递给正在执行的java程序(control-m或
    )。这是执行的限制
        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.
    
        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);
             }
    
    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;
     }
    
        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.
    
    public class SafeBufferedReader extends BufferedReader{
    
        private long millisTimeout;
    
        ( . . . )
    
        @Override
        public int read(char[] cbuf, int off, int len) throws IOException {
            try {
                waitReady();
            } catch(IllegalThreadStateException e) {
                return 0;
            }
            return super.read(cbuf, off, len);
        }
    
        protected void waitReady() throws IllegalThreadStateException, IOException {
            if(ready()) return;
            long timeout = System.currentTimeMillis() + millisTimeout;
            while(System.currentTimeMillis() < timeout) {
                if(ready()) return;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    break; // Should restore flag
                }
            }
            if(ready()) return; // Just in case.
            throw new IllegalThreadStateException("Read timed out");
        }
    }
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.Reader;
    import java.nio.CharBuffer;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Stream;
    
    /**
     * 
     * readLine
     * 
     * @author Dario
     *
     */
    public class SafeBufferedReader extends BufferedReader{
    
        private long millisTimeout;
    
        private long millisInterval = 100;
    
        private int lookAheadLine;
    
        public SafeBufferedReader(Reader in, int sz, long millisTimeout) {
            super(in, sz);
            this.millisTimeout = millisTimeout;
        }
    
        public SafeBufferedReader(Reader in, long millisTimeout) {
            super(in);
            this.millisTimeout = millisTimeout;
        }
    
    
    
        /**
         * This is probably going to kill readLine performance. You should study BufferedReader and completly override the method.
         * 
         * It should mark the position, then perform its normal operation in a nonblocking way, and if it reaches the timeout then reset position and throw IllegalThreadStateException
         * 
         */
        @Override
        public String readLine() throws IOException {
            try {
                waitReadyLine();
            } catch(IllegalThreadStateException e) {
                //return null; //Null usually means EOS here, so we can't.
                throw e;
            }
            return super.readLine();
        }
    
        @Override
        public int read() throws IOException {
            try {
                waitReady();
            } catch(IllegalThreadStateException e) {
                return -2; // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
            }
            return super.read();
        }
    
        @Override
        public int read(char[] cbuf) throws IOException {
            try {
                waitReady();
            } catch(IllegalThreadStateException e) {
                return -2;  // I'd throw a runtime here, as some people may just be checking if int < 0 to consider EOS
            }
            return super.read(cbuf);
        }
    
        @Override
        public int read(char[] cbuf, int off, int len) throws IOException {
            try {
                waitReady();
            } catch(IllegalThreadStateException e) {
                return 0;
            }
            return super.read(cbuf, off, len);
        }
    
        @Override
        public int read(CharBuffer target) throws IOException {
            try {
                waitReady();
            } catch(IllegalThreadStateException e) {
                return 0;
            }
            return super.read(target);
        }
    
        @Override
        public void mark(int readAheadLimit) throws IOException {
            super.mark(readAheadLimit);
        }
    
        @Override
        public Stream<String> lines() {
            return super.lines();
        }
    
        @Override
        public void reset() throws IOException {
            super.reset();
        }
    
        @Override
        public long skip(long n) throws IOException {
            return super.skip(n);
        }
    
        public long getMillisTimeout() {
            return millisTimeout;
        }
    
        public void setMillisTimeout(long millisTimeout) {
            this.millisTimeout = millisTimeout;
        }
    
        public void setTimeout(long timeout, TimeUnit unit) {
            this.millisTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
        }
    
        public long getMillisInterval() {
            return millisInterval;
        }
    
        public void setMillisInterval(long millisInterval) {
            this.millisInterval = millisInterval;
        }
    
        public void setInterval(long time, TimeUnit unit) {
            this.millisInterval = TimeUnit.MILLISECONDS.convert(time, unit);
        }
    
        /**
         * This is actually forcing us to read the buffer twice in order to determine a line is actually ready.
         * 
         * @throws IllegalThreadStateException
         * @throws IOException
         */
        protected void waitReadyLine() throws IllegalThreadStateException, IOException {
            long timeout = System.currentTimeMillis() + millisTimeout;
            waitReady();
    
            super.mark(lookAheadLine);
            try {
                while(System.currentTimeMillis() < timeout) {
                    while(ready()) {
                        int charInt = super.read();
                        if(charInt==-1) return; // EOS reached
                        char character = (char) charInt;
                        if(character == '\n' || character == '\r' ) return;
                    }
                    try {
                        Thread.sleep(millisInterval);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt(); // Restore flag
                        break;
                    }
                }
            } finally {
                super.reset();
            }
            throw new IllegalThreadStateException("readLine timed out");
    
        }
    
        protected void waitReady() throws IllegalThreadStateException, IOException {
            if(ready()) return;
            long timeout = System.currentTimeMillis() + millisTimeout;
            while(System.currentTimeMillis() < timeout) {
                if(ready()) return;
                try {
                    Thread.sleep(millisInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // Restore flag
                    break;
                }
            }
            if(ready()) return; // Just in case.
            throw new IllegalThreadStateException("read timed out");
        }
    
    }