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

具体来说,问题是写一个像这样的方法:

int maybeRead(InputStream in, long timeout)

如果数据在'timeout'毫秒内可用,则返回值与in.read()相同,否则为-2。 在方法返回之前,任何派生线程都必须退出。

为了避免争论,这里的主题java.io.InputStream,由Sun(任何Java版本)记录。 请注意,这不像看起来那么简单。 以下是Sun的文档直接支持的一些事实。

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

  • 在Reader或InterruptibleChannel中包装InputStream不会有帮助,因为所有这些类都可以做的是InputStream的调用方法。 如果可以使用这些类,就可以编写一个直接在InputStream上执行相同逻辑的解决方案。

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

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

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


  • 使用inputStream.available()

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

    我发现相反 - 它总是返回可用字节数的最佳值。 InputStream.available() Javadoc:

    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.
    

    由于时间/过时,估计是不可避免的。 这个数字可能是一次性低估,因为新数据不断到达。 然而它在下一次通话中总是“赶上” - 它应该解释所有到达的数据,即在新通话时刻到达的数据栏。 有数据时永久返回0不符合上述条件。

    First Caveat: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 ,您的程序仅在您的命令行程序System.in时接收输入。 如果您使用文件重定向/管道(例如somefile> java myJavaApp或somecommand | java myJavaApp),则通常会立即移交输入数据。 但是,如果您手动输入输入,则数据切换可能会延迟。 例如,使用Windows cmd.exe外壳,数据将缓存在cmd.exe外壳中。 数据仅在回车(control-m或<enter> )后传递给正在执行的java程序。 这是执行环境的限制。 当然,只要shell缓冲数据,InputStream.available()将返回0,这是正确的行为; 那时没有可用的数据。 只要数据可从shell获得,该方法返回值> 0.注意:Cygwin也使用cmd.exe。

    最简单的解决方案(无阻塞,所以不需要超时)

    只需使用这个:

        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.
    

    假设你的流不支持套接字(所以你不能使用Socket.setSoTimeout() ),我认为解决这类问题的标准方法是使用Future。

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

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

    我有编写器写入一些数据,然后在写入最后一段数据并关闭流之前等待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);
    

    正常的阅读方式如下。 读取将无限期地阻塞数据,因此以5秒完成:

        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
    

    如果还有一个更基本的问题,比如作者没有回应,读者会永远阻止。 如果我在将来包装阅读,我可以控制超时,如下所示:

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

    其输出:

    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)
    

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


    我会质疑问题陈述,而不是盲目接受。 您只需要从控制台或通过网络超时。 如果后者有Socket.setSoTimeout()HttpURLConnection.setReadTimeout() ,它们都可以完成所需的工作,只要您在构建/获取它们时正确设置它们即可。 如果你只有InputStream,那么在应用程序的后面将它留在任意点是糟糕的设计,导致实现非常尴尬。

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

    上一篇: Is it possible to read from a InputStream with a timeout?

    下一篇: Different ways of loading a file as an InputStream