Принудительная очистка на GZIPOutputStream в Java

Мы работаем над программой, в которой нам нужно очистить (принудительно сжать и отправить данные) GZIPOutputStream. Проблема заключается в том, что метод сброса GZIPOutputStream не работает должным образом (принудительное сжатие и отправка данных), вместо этого поток ожидает больше данных для эффективного сжатия данных.

Когда вы вызываете финиш, данные сжимаются и отправляются по выходному потоку, но GZIPOutputStream (не базовый поток) будет закрыт, поэтому мы не сможем записать больше данных, пока не создадим новый GZIPOutputStream, который будет стоить времени и производительности.

Надеюсь, что любой может помочь с этим.

С наилучшими пожеланиями.

6 ответов

Я еще не пробовал, и этот совет не будет полезен, пока у нас не будет Java 7, но документация для GZIPOutputStream "s flush() метод, унаследованный от DeflaterOutputStream полагается на режим промывки, указанный во время строительства с syncFlush аргумент (связанный с Deflater#SYNC_FLUSH) решить, следует ли сбрасывать ожидающие данные для сжатия. это syncFlush аргумент также принимается GZIPOutputStream во время строительства.

Похоже, вы хотите использовать либо Deflator#SYNC_FLUSH или, может быть, даже Deflater#FULL_FLUSH, но, прежде чем копать так далеко, сначала попробуйте поработать с двумя или четырьмя аргументами GZIPOutputStream конструктор и пас true для syncFlush аргумент. Это активирует желаемое поведение промывки.

Я не нашел другой ответ на работу. Он все еще отказывался сбрасывать, потому что нативный код, который использует GZIPOutputStream, удерживает данные.

К счастью, я обнаружил, что кто-то реализовал FlushableGZIPOutputStream как часть проекта Apache Tomcat. Вот волшебная часть:

@Override
public synchronized void flush() throws IOException {
    if (hasLastByte) {
        // - do not allow the gzip header to be flushed on its own
        // - do not do anything if there is no data to send

        // trick the deflater to flush
        /**
         * Now this is tricky: We force the Deflater to flush its data by
         * switching compression level. As yet, a perplexingly simple workaround
         * for
         * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html
         */
        if (!def.finished()) {
            def.setLevel(Deflater.NO_COMPRESSION);
            flushLastByte();
            flagReenableCompression = true;
        }
    }
    out.flush();
}

Вы можете найти весь класс в этой банке (если вы используете Maven):

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-coyote</artifactId>
    <version>7.0.8</version>
</dependency>

Или просто пойти и взять исходный код FlushableGZIPOutputStream.java

Он выпущен под лицензией Apache-2.0.

Этот код отлично работает для меня в моем приложении.

public class StreamingGZIPOutputStream extends GZIPOutputStream {

    public StreamingGZIPOutputStream(OutputStream out) throws IOException {
        super(out);
    }

    @Override
    protected void deflate() throws IOException {
        // SYNC_FLUSH is the key here, because it causes writing to the output
        // stream in a streaming manner instead of waiting until the entire
        // contents of the response are known.  for a large 1 MB json example
        // this took the size from around 48k to around 50k, so the benefits
        // of sending data to the client sooner seem to far outweigh the
        // added data sent due to less efficient compression
        int len = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH);
        if (len > 0) {
            out.write(buf, 0, len);
        }
    }

}

Ошибка ID 4813885 решает эту проблему. Комментарий "DamonHD", представленный 9 сентября 2006 г. (примерно на половине отчета об ошибке), содержит пример FlushableGZIPOutputStream которую он построил на вершине Jazzlib's net.sf.jazzlib.DeflaterOutputStream,

Для справки вот (переформатированный) фрагмент:

/**
 * Substitute for GZIPOutputStream that maximises compression and has a usable
 * flush(). This is also more careful about its output writes for efficiency,
 * and indeed buffers them to minimise the number of write()s downstream which
 * is especially useful where each write() has a cost such as an OS call, a disc
 * write, or a network packet.
 */
public class FlushableGZIPOutputStream extends net.sf.jazzlib.DeflaterOutputStream {
    private final CRC32 crc = new CRC32();
    private final static int GZIP_MAGIC = 0x8b1f;
    private final OutputStream os;

    /** Set when input has arrived and not yet been compressed and flushed downstream. */
    private boolean somethingWritten;

    public FlushableGZIPOutputStream(final OutputStream os) throws IOException {
        this(os, 8192);
    }

    public FlushableGZIPOutputStream(final OutputStream os, final int bufsize) throws IOException {
        super(new FilterOutputStream(new BufferedOutputStream(os, bufsize)) {
            /** Suppress inappropriate/inefficient flush()es by DeflaterOutputStream. */
            @Override
            public void flush() {
            }
        }, new net.sf.jazzlib.Deflater(net.sf.jazzlib.Deflater.BEST_COMPRESSION, true));
        this.os = os;
        writeHeader();
        crc.reset();
    }

    public synchronized void write(byte[] buf, int off, int len) throws IOException {
        somethingWritten = true;
        super.write(buf, off, len);
        crc.update(buf, off, len);
    }

    /**
     * Flush any accumulated input downstream in compressed form. We overcome
     * some bugs/misfeatures here so that:
     * <ul>
     * <li>We won't allow the GZIP header to be flushed on its own without real compressed
     * data in the same write downstream. 
     * <li>We ensure that any accumulated uncompressed data really is forced through the 
     * compressor.
     * <li>We prevent spurious empty compressed blocks being produced from successive 
     * flush()es with no intervening new data.
     * </ul>
     */
    @Override
    public synchronized void flush() throws IOException {
        if (!somethingWritten) { return; }

        // We call this to get def.flush() called,
        // but suppress the (usually premature) out.flush() called internally.
        super.flush();

        // Since super.flush() seems to fail to reliably force output, 
        // possibly due to over-cautious def.needsInput() guard following def.flush(),
        // we try to force the issue here by bypassing the guard.
        int len;
        while((len = def.deflate(buf, 0, buf.length)) > 0) {
            out.write(buf, 0, len);
        }

        // Really flush the stream below us...
        os.flush();

        // Further flush()es ignored until more input data data written.
        somethingWritten = false;
    }

    public synchronized void close() throws IOException {
        if (!def.finished()) {
            def.finish();
            do {
                int len = def.deflate(buf, 0, buf.length);
                if (len <= 0) { 
                    break;
                }
                out.write(buf, 0, len);
            } while (!def.finished());
        }

        // Write trailer
        out.write(generateTrailer());

        out.close();
    }

    // ...
}

Вы можете найти это полезным.

Есть такая же проблема на Android также. Ответ приемщика не работает, потому что def.setLevel(Deflater.NO_COMPRESSION); выбрасывает исключение. Согласно flush Метод, который изменяет уровень сжатия Deflater, Поэтому я полагаю, что изменение сжатия следует вызывать перед записью данных, но я не уверен.

Есть 2 других варианта:

  • если уровень API вашего приложения выше 19, тогда вы можете попробовать использовать конструктор с параметром syncFlush
  • другое решение использует jzlib.

как сказал @seh, это прекрасно работает:

      ByteArrayOutputStream stream = new ByteArrayOutputStream();

// the second param need to be true
GZIPOutputStream gzip = new GZIPOutputStream(stream,  true);
gzip.write( .. );
gzip.flush();

...
gzip.close()
Другие вопросы по тегам