2012-01-17 73 views
4

我目前正在嘗試編寫一個自定義流代理(讓我們以這種方式調用它),它可以更改給定輸入流中的內容並生成修改後的輸出(如有必要)。這個要求是非常必要的,因爲有時我必須修改我的應用程序中的流(例如,在運行時真正地壓縮數據)。下面的類很容易,它使用內部緩衝。僅使用InputStream和OutputStream抽象在Java中進行壓縮(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格式和deflate壓縮是專爲即時工作而設計的。

可能我在某些時候對deflate壓縮算法的細節有誤。我真的錯過了什麼?也許可能有另一種方法來編寫一個「流代理」,以使其行爲與我希望的一樣工作......我如何才能將流中的數據壓縮爲僅受流限制?

在此先感謝。


UPD:以下基本版本的作品相當不錯的用deflater和吹氣:

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 2012-01-17 16:54:21

+0

'ByteArrayOutputStream'!='BufferedOutputStream'。非常非常。 – Viruzzo 2012-01-17 16:55:52

回答

3

我不相信DeflaterOutputStream.flush()做任何有意義的事情。 deflater會積累數據,直到它有東西寫入底層流。強制剩餘數據的唯一方法是致電DeflaterOutputStream.finish()。然而,這不適用於你當前的實現,因爲在完全寫完之前你不能完成調用。

寫入壓縮流並在同一線程中讀取它實際上非常困難。在RMIIO項目中,我實際上是這樣做的,但是您需要一個任意大小的中間輸出緩衝區(並且基本上需要將數據推入,直到出現壓縮的另一端,然後才能讀取它)。您可能可以使用該項目中的某些util類來完成您想要執行的操作。

+0

「你基本上需要推入數據,直到出現壓縮的東西出現在另一端」這是最大的問題之一(除非你能負擔得起一次壓縮整個內容);一個效率低下(但簡單)的解決方案是將數據壓縮成離散的「數據包」,只要你能夠進行解壓縮。 – Viruzzo 2012-01-17 18:36:07

+0

剛剛添加了'void afterFlush(O outputStream)throws IOException;'代碼示例的偵聽器。最後它壓縮了「lorem ipsum」文本示例。感謝您指向'.finish()'。 :) – 2012-01-18 11:00:52

+0

@LyubomyrShaydariv - 你意識到,但是,一旦你打完了,你的壓縮流就完成了。您將永遠無法處理超過512個字節的壓縮數據。你目前的代碼並不是真正的「通用」解決方案。 – jtahlborn 2012-01-18 12:57:18

3

爲什麼不使用GZipOutputStream?

我有點迷路。但我應該簡單地使用原始的outputStream當我不想壓縮,new GZipOutputStream(outputStream)當我想要壓縮。就這樣。無論如何,檢查你是否正在刷新輸出流。

Gzip已VS拉鍊

另外:有一件事是GZIP(壓縮數據流,這是你在做什麼),還有一件事是寫一個有效的zip文件(文件頭,文件目錄項(頭,數據)*)。檢查ZipOutputStream

+0

謝謝你的回覆。使用GZipOutputStream與ZipOutputStream一樣沒有效果。我只是簡單地將輸出流完全修剪:446中的「Lorem ipsum ...」變成了我在問題中提到的兩個字節。我無法直接使用OutputStream,因爲需要將InputStream委託給JDBC準備好的語句(因爲可能有巨大的傳入數據)。這就是爲什麼我在尋找一個類,像MyApp(inputStream) - > [compressor] - > JDBC(inputStream)。 – 2012-01-17 17:18:13

1

要小心,如果你的地方使用方法 int read(byte b[], int off, int len)和異常的情況下,行 final int iBufferedBytes = iStream.read(iBuffer);

你會陷入無限循環