从OutputStream创建InputStream的最有效方法


84

此页面:http : //blog.ostermiller.org/convert-java-outputstream-inputstream 描述如何从OutputStream创建InputStream:

new ByteArrayInputStream(out.toByteArray())

其他选择是使用PipedStreams和新线程,这很麻烦。

我不喜欢将许多兆字节复制到新的内存字节数组中的想法。是否有一个图书馆可以更有效地做到这一点?

编辑:

根据劳伦斯·贡萨尔维斯(Laurence Gonsalves)的建议,我尝试了PipedStreams,结果发现它们并不难处理。这是clojure中的示例代码:

(defn #^PipedInputStream create-pdf-stream [pdf-info]
  (let [in-stream (new PipedInputStream)
        out-stream (PipedOutputStream. in-stream)]
    (.start (Thread. #(;Here you write into out-stream)))
    in-stream))

Answers:


72

如果您不想一次将所有数据都复制到内存缓冲区中,那么您将必须拥有使用OutputStream的代码(生产者)和使用InputStream的代码(消费者)在同一线程中交替进行,或在两个单独的线程中同时进行操作。让它们在同一线程中操作可能比使用两个单独的线程复杂得多,容易出错(您需要确保使用者永远不会阻塞等待输入,否则您将实际上陷入僵局),并且有必要这样做生产者和消费者在同一个循环中运行,这似乎太紧密了。

因此,使用第二个线程。确实没有那么复杂。您链接到的页面有一个很好的例子:

  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
  ).start();
  class2.processDataFromInputStream(in);

我认为您还需要为每个使用者线程创建新的PipedInputStream。如果您从另一个线程读取Pipe,它将给您一个错误。
丹尼斯·图尔斯基

@Lawrence:我不理解您使用2个线程的理由...除非要求从InputStream读取的所有字符都必须及时写入OutputStream。
Stephen C

8
斯蒂芬:在写东西之前,你无法阅读。因此,只有一个线程,您要么需要首先编写所有内容(创建Vagif想要避免的大型内存数组),要么需要让它们交替使用,请非常小心,以使读者不要阻塞等待输入的时间(因为如果他这样做了) ,编写者也将永远无法执行)。
劳伦斯·贡萨尔维斯

1
在容器可能正在运行许多自己线程的JEE环境中使用此建议是否安全?
Toskan

2
@Toskan如果new Thread由于某种原因在您的容器中不合适,请查看是否有可以使用的线程池。
劳伦斯·贡萨尔维斯

14

还有另一个名为EasyStream的开源库,该库以透明方式处理管道和线程。如果一切顺利的话,这并不复杂。当出现问题时(请看Laurence Gonsalves的示例)

class1.putDataOnOutputStream(out);

引发异常。在该示例中,线程仅完成而异常丢失,而外部InputStream可能被截断。

Easystream处理异常传播和我已经调试大约一年的其他棘手问题。(我是图书馆的管理者:显然我的解决方案是最好的解决方案;))下面是一个如何使用它的示例:

final InputStreamFromOutputStream<String> isos = new InputStreamFromOutputStream<String>(){
 @Override
 public String produce(final OutputStream dataSink) throws Exception {
   /*
    * call your application function who produces the data here
    * WARNING: we're in another thread here, so this method shouldn't 
    * write any class field or make assumptions on the state of the outer class. 
    */
   return produceMydata(dataSink)
 }
};

还有一个很好的介绍,其中介绍了将OutputStream转换为InputStream的所有其他方法。值得一看。


1
有关使用其课程的教程,请访问code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor

9

一个避免复制缓冲区的简单解决方案是创建一个特殊用途ByteArrayOutputStream

public class CopyStream extends ByteArrayOutputStream {
    public CopyStream(int size) { super(size); }

    /**
     * Get an input stream based on the contents of this output stream.
     * Do not use the output stream after calling this method.
     * @return an {@link InputStream}
     */
    public InputStream toInputStream() {
        return new ByteArrayInputStream(this.buf, 0, this.count);
    }
}

根据需要写入上述输出流,然后调用toInputStream以获取基础缓冲区上的输入流。在那之后,将输出流视为已关闭。


7

我认为将InputStream连接到OutputStream的最好方法是通过管道流-在java.io包中可用,如下所示:

// 1- Define stream buffer
private static final int PIPE_BUFFER = 2048;

// 2 -Create PipedInputStream with the buffer
public PipedInputStream inPipe = new PipedInputStream(PIPE_BUFFER);

// 3 -Create PipedOutputStream and bound it to the PipedInputStream object
public PipedOutputStream outPipe = new PipedOutputStream(inPipe);

// 4- PipedOutputStream is an OutputStream, So you can write data to it
// in any way suitable to your data. for example:
while (Condition) {
     outPipe.write(mByte);
}

/*Congratulations:D. Step 4 will write data to the PipedOutputStream
which is bound to the PipedInputStream so after filling the buffer
this data is available in the inPipe Object. Start reading it to
clear the buffer to be filled again by the PipedInputStream object.*/

我认为此代码有两个主要优点:

1-除缓冲区外,没有其他内存消耗。

2-您不需要手动处理数据排队


1
这真是太棒了,但是javadocs说如果在同一线程中读写它们,则可能会死锁。我希望他们已经用NIO更新了它!
Nate Glenn

1

由于死锁的机会增加,理解代码的难度增加以及处理异常的问题,我通常会尝试避免创建单独的线程。

这是我建议的解决方案:ProducerInputStream通过重复调用produceChunk()来创建大块内容:

public abstract class ProducerInputStream extends InputStream {

    private ByteArrayInputStream bin = new ByteArrayInputStream(new byte[0]);
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();

    @Override
    public int read() throws IOException {
        int result = bin.read();
        while ((result == -1) && newChunk()) {
            result = bin.read();
        }
        return result;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = bin.read(b, off, len);
        while ((result == -1) && newChunk()) {
            result = bin.read(b, off, len);
        }
        return result;
    }

    private boolean newChunk() {
        bout.reset();
        produceChunk(bout);
        bin = new ByteArrayInputStream(bout.toByteArray());
        return (bout.size() > 0);
    }

    public abstract void produceChunk(OutputStream out);

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.