2012-01-17 13 views
4

私は現在、指定された入力ストリームからコンテンツを変更し、必要に応じて出力を変更できるカスタムストリームプロキシを作成しようとしています(そのように呼びます)。アプリケーションでストリームを変更する必要がある場合があります(例えば、実際にデータを圧縮する場合は、をオンフックしてください))この要件は本当に必要です。次のクラスはかなり簡単で、内部バッファリングを使用します。JavaでInputStreamとOutputStreamの抽象化のみを使用して圧縮を圧縮(ZIP)します。可能?

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」>「ABC」):

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をオンにしてテストメソッドを呼び出すと、実際には奇妙に動作し、ほとんど何も書き込まれません(ヘッダ78 9Cを省略した場合)。私はdeflaterクラスが私が使いたいと思うアプローチに合うように設計されていないかもしれないと思うが、私はいつもZIP形式と圧縮の圧縮はオンザフライで動作するように設計されていると信じている。

おそらく、私は、デフレート圧縮アルゴリズムの仕様について間違っています。私が実際に見逃していることは?おそらく、ストリーム・プロキシーを書いて、動作させたいのと同じように動作する別の方法があるかもしれません。ストリームでのみ制限されているデータを圧縮するにはどうすればいいですか?

ありがとうございます。


UPD:次の基本的なバージョンデフレータとインフレータとかなりいい作品:GZipOutputStreamを使用していないのはなぜ

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

}

+1

を動けなくなり、私は少し迷ってしまいました、注意してください。しかし、圧縮したくないときは元の 'outputStream'を使い、圧縮したいときは' new GZipOutputStream(outputStream) 'を使います。それで全部です。とにかく、出力ストリームをフラッシュしていることを確認してください。 – helios

+0

'ByteArrayOutputStream'!=' BufferedOutputStream'です。まさにその通り。 – Viruzzo

答えて

3

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

実際には、圧縮されたストリームを書き込んで同じスレッド内で読むのは非常に困難です。RMIIOプロジェクトで私は実際にこれを行いますが、任意のサイズの中間出力バッファが必要です(そして、何かが圧縮されるまでデータをプッシュする必要があります。あなたは、あなたがしたいことを達成するためにそのプロジェクトでutilクラスのいくつかを使うことができます。

+0

"あなたは基本的にデータが他端で圧縮されるまでデータをプッシュする必要があります"これは最大の問題の1つです(コンテンツ全体を一度に圧縮する余裕がない限り)。非効率的な(しかし簡単な)ソリューションは、解凍を行うことができれば、個別の "パケット"でデータを圧縮することです。 – Viruzzo

+0

リスナーを 'void afterFlush(O outputStream)throws IOException; 'というコードサンプルに追加しました。最後に、「lorem ipsum」テキストサンプルを圧縮しました。 '.finish()'を指してくれてありがとう。 :) –

+0

@LyubomyrShaydariv - あなたが一度仕上げを呼ぶと、あなたの圧縮されたストリームが完了したことに気づきます。 512バイト以上の圧縮データを処理することはできません。あなたの現在のコードは実際には "一般的な"解決策ではありません。 – jtahlborn

3

私は少し迷っています。しかし、圧縮したくないときは元のoutputStreamを使い、圧縮したいときはnew GZipOutputStream(outputStream)を使います。それで全部です。とにかく、出力ストリームをフラッシュしていることを確認してください。ジップ

gzipはまた:一つのことは、GZIPである(それはあなたがやっていることだ、ストリームを圧縮)し、別のものは、有効なZIPファイル(ファイルヘッダ、ファイルのディレクトリを書いて、エントリ(ヘッダ、データ)*)。 ZipOutputStreamを確認してください。

+0

ありがとうございます。 GZipOutputStreamを使用すると、ZipOutputStreamと同様に効果がありません。私は単純に出力ストリームを完全にトリムします:446の "Lorem ipsum ..."は、私が質問で言及した2バイトになります。 OutputStreamを直接使用することはできません。なぜなら、InputStreamをJDBC準備文に委譲させる必要があるからです(おそらく巨大な入力データのため)。だから私はMyApp(inputStream) - > [compressor] - > JDBC(inputStream)のようなプロキシであるクラスを探しているのです。 –

1

はどこかにあなたが方法 int read(byte b[], int off, int len)を使用して、例外の場合のラインで final int iBufferedBytes = iStream.read(iBuffer);

場合は、無限ループに

関連する問題