簡體   English   中英

Java 的 GZIPOutputStream 拒絕刷新

[英]Java's GZIPOutputStream refuses to flush

這是演示我的問題的 JUnit 測試:

package stream;

import static org.junit.jupiter.api.Assertions.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.junit.jupiter.api.Test;

class StreamTest {
    public static class LoopbackStream {
        private final byte[] END_MARKER = new byte[0];
        private final ArrayBlockingQueue<byte[]> queue = new ArrayBlockingQueue<>(1024);

        public OutputStream getOutputStream() {
            return new OutputStream() {
                @Override
                public void write(int b) throws IOException {
                    this.write(new byte[] { (byte) b });
                }

                @Override
                public void write(byte[] b, int off, int len) {
                    try {
                        queue.put(Arrays.copyOfRange(b, off, len - off));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }

                @Override
                public void close() {
                    try {
                        queue.put(END_MARKER);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            };
        }

        public InputStream getInputStream() {
            return new InputStream() {
                private boolean finished = false;
                private ByteBuffer current = ByteBuffer.wrap(new byte[0]);

                @Override
                public int read() {
                    if (ensureData()) {
                        return Byte.toUnsignedInt(current.get());
                    } else {
                        return -1;
                    }
                }

                @Override
                public int read(byte[] b, int off, int len) {
                    if (ensureData()) {
                        int position = current.position();
                        current.get(b, off, Math.min(len, current.remaining()));
                        return current.position() - position;
                    } else {
                        return -1;
                    }
                }

                private boolean ensureData() {
                    if (!finished && !current.hasRemaining()) {
                        try {
                            byte[] data = queue.take();
                            current = ByteBuffer.wrap(data);
                            finished = data == END_MARKER;
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return false;
                        }
                    }
                    return !finished;
                }
            };
        }

    }

    @Test
    void testVanilla() throws IOException {
        LoopbackStream objectUnderTest = new LoopbackStream();

        PrintWriter pw = new PrintWriter(new OutputStreamWriter(objectUnderTest.getOutputStream()), true);
        BufferedReader br = new BufferedReader(new InputStreamReader(objectUnderTest.getInputStream()));

        pw.println("Hello World!");

        assertEquals("Hello World!", br.readLine());
    }

    @Test
    void testVanilla2() throws IOException {
        LoopbackStream objectUnderTest = new LoopbackStream();

        PrintWriter pw = new PrintWriter(new OutputStreamWriter(objectUnderTest.getOutputStream()), true);
        BufferedReader br = new BufferedReader(new InputStreamReader(objectUnderTest.getInputStream()));

        pw.println("Hello World!");
        assertEquals("Hello World!", br.readLine());

        pw.println("Hello Otherworld!");
        assertEquals("Hello Otherworld!", br.readLine());
    }

    @Test
    void testGzipped() throws IOException {
        LoopbackStream objectUnderTest = new LoopbackStream();

        PrintWriter pw = new PrintWriter(new OutputStreamWriter(new GZIPOutputStream(objectUnderTest.getOutputStream(), true)), true);
        BufferedReader br = new BufferedReader(new InputStreamReader(new GZIPInputStream(objectUnderTest.getInputStream())));

        pw.println("Hello World!");

        assertEquals("Hello World!", br.readLine());
    }
}

有兩個單獨的測試。 一個使用 vanilla 輸入和 output 流(工作正常),另一個將這些流包裝在它們的 gzip 等效項中。

我已經使用了GZIPOutputStreamsyncFlush選項,我希望在刷新父 ZF7B44CFFAD5C52223D5498196C8A2E7BZ 時自動刷新 stream 中的任何剩余字節。 每當它執行println時,我都會使用PrintWriterautoFlush選項來刷新其數據。

有沒有更好的方法來強制GZIPOutputStreamprintln之后刷新其緩沖區?

我知道這不是您問題的完整答案,但是評論太長了...


更新:

經過進一步調查,似乎不是GZIPOutputStream不刷新(通過在public void write(byte[] b, int off, int len)方法中添加System.out.println("xy");語句,您可以看到GZIPOutputStream將兩個字節 arrays 寫入您的OutputStream :一個是 gzip stream header,另一個是編碼內容的第一行)。

似乎由於java.io.InputStreamReader (分別是它使用的sun.nio.cs.StreamDecoder )和GZIPInputStream之間的不良交互而導致讀取過程阻塞。

Basically, if the StreamDecoder needs to read bytes from the underlying stream it tries to read as many bytes as possible (as long as underlying stream reports in.available() > 0 , implying that the underlying stream can yield some more bytes without blocking)

這個檢查的問題是InflaterInputStreamGZIPInputStream的超類)總是返回1的可用字節數,即使它的源 stream沒有可用的字節( 參見 InflaterInputStream.available InflaterInputStream.available()的源

所以看起來雖然你可以逐行寫入GZIPOutputStream ,但你不能輕易地從GZIPInputStream中逐行讀取......


原答案:

問題不在於GZIPOutputStream ,問題在於拒絕讀取多個塊的boolean ensureData()方法。

以下測試也因香草流而失敗:

@Test
void testVanilla2() throws IOException {
    LoopbackStream objectUnderTest = new LoopbackStream();

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(objectUnderTest.getOutputStream()), true);
    BufferedReader br = new BufferedReader(new InputStreamReader(objectUnderTest.getInputStream()));

    pw.println("Hello World!");
    assertEquals("Hello World!", br.readLine());

    pw.println("Hello Otherworld!");
    assertEquals("Hello Otherworld!", br.readLine());
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM