4

私は現在、指定された入力ストリームからコンテンツを変更し、必要に応じて変更された出力を生成できるカスタム ストリーム プロキシ (そのように呼び出しましょう) を作成しようとしています。アプリケーションでストリームを変更する必要がある場合があるため、この要件は本当に必要です (たとえば、その場でデータを圧縮するなど)。次のクラスは非常に簡単で、内部バッファリングを使用します。

private static class ProxyInputStream extends InputStream {

    private final InputStream iStream;
    private final byte[] iBuffer = new byte[512];

    private int iBufferedBytes;

    private final ByteArrayOutputStream oBufferStream;
    private final OutputStream oStream;

    private byte[] oBuffer = emptyPrimitiveByteArray;
    private int oBufferIndex;

    ProxyInputStream(InputStream iStream, IFunction<OutputStream, ByteArrayOutputStream> oStreamFactory) {
        this.iStream = iStream;
        oBufferStream = new ByteArrayOutputStream(512);
        oStream = oStreamFactory.evaluate(oBufferStream);
    }

    @Override
    public int read() throws IOException {
        if ( oBufferIndex == oBuffer.length ) {
            iBufferedBytes = iStream.read(iBuffer);
            if ( iBufferedBytes == -1 ) {
                return -1;
            }
            oBufferIndex = 0;
            oStream.write(iBuffer, 0, iBufferedBytes);
            oStream.flush();
            oBuffer = oBufferStream.toByteArray();
            oBufferStream.reset();
        }
        return oBuffer[oBufferIndex++];
    }

}

次のように、書き込まれたすべてのバイトの前にスペース文字を追加するだけのサンプル テスト出力ストリーム ("abc" -> "ab c") もあるとします。

private static class SpacingOutputStream extends OutputStream {

    private final OutputStream outputStream;

    SpacingOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public void write(int b) throws IOException {
        outputStream.write(' ');
        outputStream.write(b);
    }

}

そして、次のテスト方法:

private static void test(final boolean useDeflater) throws IOException {
    final FileInputStream input = new FileInputStream(SOURCE);
    final IFunction<OutputStream, ByteArrayOutputStream> outputFactory = new IFunction<OutputStream, ByteArrayOutputStream>() {
        @Override
        public OutputStream evaluate(ByteArrayOutputStream outputStream) {
            return useDeflater ? new DeflaterOutputStream(outputStream) : new SpacingOutputStream(outputStream);
        }
    };
    final InputStream proxyInput = new ProxyInputStream(input, outputFactory);
    final OutputStream output = new FileOutputStream(SOURCE + ".~" + useDeflater);
    int c;
    while ( (c = proxyInput.read()) != -1 ) {
        output.write(c);
    }
    output.close();
    proxyInput.close();
}

このテスト メソッドは、ファイルの内容を読み取り、それを別のストリームに書き込むだけです。これはおそらく何らかの方法で変更できます。テスト メソッドが で実行されている場合useDeflater=false、期待されるアプローチは期待どおりに正常に機能します。しかし、テストメソッドがuseDeflater設定された状態で呼び出されると、非常に奇妙な動作をし、ほとんど何も書き込みません (header を省略した場合78 9C)。デフレーター クラスは、私が使用したいアプローチに適合するように設計されていないのではないかと思いますが、ZIP 形式とデフレート圧縮はオンフライで動作するように設計されていると常に信じていました。

おそらく、deflate 圧縮アルゴリズムの詳細について、ある時点で間違っていると思われます。本当に見逃しているものは何ですか?.おそらく、「ストリームプロキシ」を作成して、動作させたいとおりに動作させる別のアプローチがあるかもしれません...ストリームのみに制限されているデータをオンザフライで圧縮するにはどうすればよいですか?

前もって感謝します。


UPD: 次の基本的なバージョンは、デフレーターとインフレータでうまく機能します。

public final class ProxyInputStream<OS extends OutputStream> extends InputStream {

private static final int INPUT_BUFFER_SIZE = 512;
private static final int OUTPUT_BUFFER_SIZE = 512;

private final InputStream iStream;
private final byte[] iBuffer = new byte[INPUT_BUFFER_SIZE];
private final ByteArrayOutputStream oBufferStream;
private final OS oStream;
private final IProxyInputStreamListener<OS> listener;

private byte[] oBuffer = emptyPrimitiveByteArray;
private int oBufferIndex;
private boolean endOfStream;

private ProxyInputStream(InputStream iStream, IFunction<OS, ByteArrayOutputStream> oStreamFactory, IProxyInputStreamListener<OS> listener) {
    this.iStream = iStream;
    oBufferStream = new ByteArrayOutputStream(OUTPUT_BUFFER_SIZE);
    oStream = oStreamFactory.evaluate(oBufferStream);
    this.listener = listener;
}

public static <OS extends OutputStream> ProxyInputStream<OS> proxyInputStream(InputStream iStream, IFunction<OS, ByteArrayOutputStream> oStreamFactory, IProxyInputStreamListener<OS> listener) {
    return new ProxyInputStream<OS>(iStream, oStreamFactory, listener);
}

@Override
public int read() throws IOException {
    if ( oBufferIndex == oBuffer.length ) {
        if ( endOfStream ) {
            return -1;
        } else {
            oBufferIndex = 0;
            do {
                final int iBufferedBytes = iStream.read(iBuffer);
                if ( iBufferedBytes == -1 ) {
                    if ( listener != null ) {
                        listener.afterEndOfStream(oStream);
                    }
                    endOfStream = true;
                    break;
                }
                oStream.write(iBuffer, 0, iBufferedBytes);
                oStream.flush();
            } while ( oBufferStream.size() == 0 );
            oBuffer = oBufferStream.toByteArray();
            oBufferStream.reset();
        }
    }
    return !endOfStream || oBuffer.length != 0 ? (int) oBuffer[oBufferIndex++] & 0xFF : -1;
}

}

4

3 に答える 3

4

DeflaterOutputStream.flush()私はそれが何か意味のあることをするとは思わない。デフレータは、基になるストリームに書き出すものができるまでデータを蓄積します。データの残りのビットを強制的に出力する唯一の方法は、を呼び出すことDeflaterOutputStream.finish()です。ただし、これは現在の実装では機能しません。完全に書き込みが完了するまで、finishを呼び出すことができないためです。

圧縮されたストリームを作成して同じスレッド内で読み取ることは、実際には非常に困難です。RMIIOプロジェクトでは、実際にこれを行いますが、任意のサイズの中間出力バッファーが必要です(基本的に、反対側で何かが圧縮されて出力されるまでデータをプッシュする必要があります。その後、データを読み取ることができます)。そのプロジェクトでいくつかのutilクラスを使用して、やりたいことを実行できる場合があります。

于 2012-01-17T17:04:47.203 に答える
3

GZipOutputStream を使用しないのはなぜですか?

私は少し迷っています。outputStreamただし、圧縮したくない場合と圧縮したい場合は、元のファイルを単純に使用する必要がありnew GZipOutputStream(outputStream)ます。それで全部です。とにかく、出力ストリームをフラッシュしていることを確認してください。

Gzip と zip の比較

また、1 つは GZIP (ストリームを圧縮することです) であり、もう 1 つは有効な zip ファイル (ファイル ヘッダー、ファイル ディレクトリ、エントリ (ヘッダー、データ)*) を書き込むことです。確認してくださいZipOutputStream

于 2012-01-17T16:58:28.283 に答える
1

どこかでメソッドを使用 int read(byte b[], int off, int len)し、行で例外が発生した場合は 注意してください final int iBufferedBytes = iStream.read(iBuffer);

無限ループに陥ります

于 2017-07-25T12:12:47.763 に答える