6

作成時間をできるだけ少なくして、入力ストリームを複製する方法を教えてもらえますか? IS を処理する複数のメソッドに対して、入力ストリームを複数回複製する必要があります。3 つの方法を試しましたが、何らかの理由でうまくいきません。

方法 #1: stackoverflow コミュニティのおかげで、次のリンクが役立つことがわかり、プログラムにコード スニペットを組み込みました。

InputStream のクローンを作成する方法は?

ただし、このコードを使用すると、複製された入力ストリームを作成するのに最大 1 分 (10MB のファイルの場合) かかる場合があり、私のプログラムはできるだけ高速である必要があります。

    int read = 0;
    byte[] bytes = new byte[1024*1024*2];

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    while ((read = is.read(bytes)) != -1)
        bos.write(bytes,0,read);
    byte[] ba = bos.toByteArray();

    InputStream is1 = new ByteArrayInputStream(ba);
    InputStream is2 = new ByteArrayInputStream(ba);
    InputStream is3 = new ByteArrayInputStream(ba);

方法 #2: BufferedInputStream を使用して IS のクローンも試しました。これは高速でした (最も遅い作成時間 == 1 ミリ秒、最も速い == 0 ミリ秒)。しかし、処理する is1 を送信した後、is2 および is3 を処理するメソッドは、処理するものが何もないというエラーをスローしました。これは、以下の 3 つの変数すべてが同じ IS を参照しているようなものです。

    is = getFileFromBucket(path,filename);
    ...
    ...
    InputStream is1 = new BufferedInputStream(is);
    InputStream is2 = new BufferedInputStream(is);
    InputStream is3 = new BufferedInputStream(is);

方法 #3: コンパイラが嘘をついていると思います。上記の 2 つの例では、is1 の markSupported() を確認しました。true が返されたので、実行できると思いました

    is1.mark() 
    is1.reset()

あるいは単に

    is1.reset();

IS をそれぞれのメソッドに渡す前に。上記の両方の例で、無効なマークであるというエラーが表示されます。

私は今アイデアがありませんので、あなたが私に与えることができる助けを事前に感謝します.

PS 人々から受け取ったコメントから、私の状況に関していくつかのことを明確にする必要があります: 1) このプログラムは VM で実行されています 2) 入力ストリームは別のメソッドから渡されています。ローカル ファイルから読み取っていません 3) 入力ストリームのサイズが不明です

4

4 に答える 4

6

できるだけ少ない作成時間で、入力ストリームを複製する方法は? IS を処理する複数のメソッドに対して、入力ストリームを複数回複製する必要があります

ある種のカスタムReusableInputStreamクラスを作成するだけで、最初の完全な読み取りですぐに内部に書き込み 、最後のバイトが読み取らByteArrayOutputStreamれたときにそれをラップし、最後に自動的に反転される後続の完全な読み取りでまったく同じものを再利用できます。限界に達したとき。これにより、最初の試行のように 1 回の完全な読み取りから節約できます。ByteBufferByteBuffer

基本的なキックオフの例を次に示します。

public class ReusableInputStream extends InputStream {

    private InputStream input;
    private ByteArrayOutputStream output;
    private ByteBuffer buffer;

    public ReusableInputStream(InputStream input) throws IOException {
        this.input = input;
        this.output = new ByteArrayOutputStream(input.available()); // Note: it's resizable anyway.
    }

    @Override
    public int read() throws IOException {
        byte[] b = new byte[1];
        read(b, 0, 1);
        return b[0];
    }

    @Override
    public int read(byte[] bytes) throws IOException {
        return read(bytes, 0, bytes.length);
    }

    @Override
    public int read(byte[] bytes, int offset, int length) throws IOException {
        if (buffer == null) {
            int read = input.read(bytes, offset, length);

            if (read <= 0) {
                input.close();
                input = null;
                buffer = ByteBuffer.wrap(output.toByteArray());
                output = null;
                return -1;
            } else {
                output.write(bytes, offset, read);
                return read;
            }
        } else {
            int read = Math.min(length, buffer.remaining());

            if (read <= 0) {
                buffer.flip();
                return -1;
            } else {
                buffer.get(bytes, offset, read);
                return read;
            }
        }

    }

    // You might want to @Override flush(), close(), etc to delegate to input.
}

(実際のジョブは inint read(byte[], int, int)ではなく in で実行int read()されるため、呼び出し元自体もbyte[]バッファーを使用してストリーミングしている場合は高速になることが期待されることに注意してください)

次のように使用できます。

InputStream input = new ReusableInputStream(getFileFromBucket(path,filename));
IOUtils.copy(input, new FileOutputStream("/copy1.ext"));
IOUtils.copy(input, new FileOutputStream("/copy2.ext"));
IOUtils.copy(input, new FileOutputStream("/copy3.ext"));

パフォーマンスに関しては、10MB あたり 1 分というのは、ソフトウェアの問題ではなく、ハードウェアの問題である可能性が高くなります。私の 7200rpm ラップトップ ハードディスクは、1 秒未満で完了します。

于 2012-11-09T15:56:50.220 に答える
3

ただし、このコードを使用すると、複製された入力ストリームを作成するのに最大1分(10MBのファイルの場合)かかる可能性があり、私のプログラムは可能な限り高速である必要があります。

ストリームを適切にコピーするには時間がかかり、(一般的に)それがストリームのクローンを作成する唯一の方法です。問題の範囲を狭めない限り、パフォーマンスが大幅に向上する可能性はほとんどありません。

改善が可能な状況は次のとおりです。

  • ストリーム内のバイト数を事前に知っている場合は、最終的なバイト配列に直接読み取ることができます。

  • データがファイルからのものであることがわかっている場合は、ファイルのメモリマップトバッファを作成できます。

しかし、根本的な問題は、大量のバイトを移動するのに時間がかかることです。そして、(質問のコードを使用して)10Mbファイルに1分かかるという事実は、本当のボトルネックがJavaにまったくないことを示唆しています。

于 2012-11-09T03:06:06.623 に答える
2

最初のアプローチに関しては、すべてのバイトを ByteArrayOutputStream に入れることからなるアプローチ:

  • まず、このアプローチは大量のメモリを消費します。十分なメモリが割り当てられた状態で JVM が起動することを確認しないと、ストリームの処理中に動的にメモリを要求する必要があり、これには時間がかかります。
  • ByteArrayOutputStream は、最初は 32 バイトのバッファーで作成されます。何かを入れようとするたびに、既存のバイト配列に収まらない場合は、新しい大きな配列が作成され、古いバイトが新しいバイト配列にコピーされます。毎回 2MB の入力を使用しているため、ByteArrayOutputStream がそのデータをより大きな配列に何度もコピーし、その配列のサイズを毎回 2MB ずつ増やしています。
  • 古い配列はガベージであるため、メモリがガベージ コレクターによって回収され、コピー プロセスがさらに遅くなる可能性があります。
  • おそらく、初期バッファー サイズを指定するコンストラクターを使用して ByArrayOutputStream を定義する必要があります。サイズを正確に設定すればするほど、必要な中間コピーが少なくなるため、処理が速くなります。

2番目のアプローチは偽物です。同じ入力ストリームを他の異なるストリーム内で装飾し、物事が機能することを期待することはできません。バイトが 1 つのストリームによって消費されると、内側のストリームも使い果たされ、他のストリームに正確なデータを提供できなくなります。

回答を拡張する前に、別のスレッドで実行されている入力ストリームのコピーを受け取ることを他の方法で期待していますか? もしそうなら、これは PipedOutputStream と PipedInputStream の作業のように聞こえますか?

于 2012-11-09T06:49:18.267 に答える
1

個別のメソッドを並行して実行する予定ですか、それとも順次実行する予定ですか?順次の場合、入力ストリームのクローンを作成する理由がないため、各ストリームを管理するためにスレッドをスピンオフすることを計画していると想定する必要があります。

私は今これをテストするためにコンピューターの近くにいませんが、たとえば1024バイトのチャンクで入力を読み取り、それらのチャンク(またはチャンクの配列コピー)を入力ストリームがスレッドの終わりに接続された出力ストリーム。利用可能なデータがない場合などは、読者にブロックしてもらいます。

于 2012-11-09T06:33:06.220 に答える