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

回答:


72

すべてのデータを一度にメモリ内バッファにコピーしたくない場合は、OutputStream(プロデューサー)を使用するコードとInputStream(コンシューマー)を使用するコードが必要になります。 )同じスレッドで交互に実行するか、2つの別々のスレッドで同時に動作します。それらを同じスレッドで動作させることは、2つの別々のスレッドを使用するよりもはるかに複雑であり、エラーが発生しやすく(コンシューマーが入力の待機をブロックしないようにする必要があります。そうしないと、効果的にデッドロックが発生します)、必要になります。プロデューサーとコンシューマーを同じループで実行することは、あまりにも緊密に結合されているように見えます。

したがって、2番目のスレッドを使用します。それほど複雑ではありません。リンクしたページには完璧な例があります。

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

また、コンシューマースレッドごとに新しいPipedInputStreamを作成する必要があると思います。別のスレッドからパイプから読み取ると、エラーが発生します。
Denis Tulskiy 2009

@Lawrence:2つのスレッドを使用する理由がわかりません... InputStreamから読み取られたすべての文字がタイムリーにOutputStreamに書き込まれる必要がある場合を除きます。
スティーブンC

8
スティーブン:書かれるまで何かを読むことはできません。したがって、スレッドが1つしかない場合は、最初にすべてを書き込む必要があります(Vagifが避けたい大きなメモリ内配列を作成する)か、リーダーが入力の待機をブロックしないように非常に注意して、それらを交互に作成する必要があります( 、ライターも実行できません)。
ローレンスゴンサルベス

1
この提案は、コンテナがおそらく多くの独自のスレッドを実行しているJEE環境で安全に使用できますか?
Toskan 2012

2
@Toskannew Threadが何らかの理由でコンテナ内で適切でない場合は、使用できるスレッドプールがあるかどうかを確認してください。
ローレンスゴンサルベス2012

14

パイプとスレッドを透過的に処理するEasyStreamと呼ばれる別のオープンソースライブラリがあります。すべてがうまくいけば、それはそれほど複雑ではありません。(Laurence Gonsalvesの例を見ると)問題が発生します

class1.putDataOnOutputStream(out);

例外をスローします。その例では、スレッドは単純に完了し、例外は失われますが、外側InputStreamは切り捨てられる可能性があります。

Easystreamは、私が約1年間デバッグしてきた例外の伝播やその他の厄介な問題に対処します。(私はライブラリの管理者です:明らかに私の解決策が最良のものです;))これはそれを使用する方法の例です:

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 /

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.*/

私の意見では、このコードには2つの主な利点があります。

1-バッファ以外のメモリの追加消費はありません。

2-データキューイングを手動で処理する必要はありません


1
これは素晴らしいことですが、javadocsによると、同じスレッドでこれらを読み書きすると、デッドロックが発生する可能性があります。彼らがこれをNIOで更新してくれたらよかったのに!
ネイトグレン

1

私は通常、デッドロックの可能性が高くなり、コードを理解するのが難しくなり、例外を処理する際の問題があるため、別のスレッドを作成しないようにしています。

これが私の提案する解決策です:produceChunk()への繰り返しの呼び出しによってチャンクでコンテンツを作成するProducerInputStream:

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

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.