java gzip decompress
Fuerza de descarga en un GZIPOutputStream en java (5)
estamos trabajando en un programa en el que debemos vaciar (forzar la compresión y enviar datos) un GZIPOutputStream. El problema es que el método de descarga de GZIPOutputStream no funciona como se esperaba (forzar la compresión y el envío de datos), en cambio, el Stream espera más datos para una compresión de datos eficiente.
Cuando finaliza la llamada, los datos se comprimen y envían a través del flujo de salida, pero GZIPOutputStream (no el flujo subyacente) se cerrará, por lo que no podemos escribir más datos hasta que creamos un nuevo GZIPOutputStream, que cuesta tiempo y rendimiento.
Espero que alguien pueda ayudar con esto.
Atentamente.
Este código está funcionando muy bien para mí en mi aplicación.
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);
}
}
}
Hay el mismo problema en Android
también. La respuesta del def.setLevel(Deflater.NO_COMPRESSION);
no funciona porque def.setLevel(Deflater.NO_COMPRESSION);
lanza la excepción. Según el método de flush
, cambia el nivel de compresión de Deflater
. Así que supongo que debería cambiarse la compresión antes de escribir datos, pero no estoy seguro.
Hay otras 2 opciones:
- Si el nivel de API de su aplicación es mayor que 19, entonces puede intentar usar el constructor con syncFlush param
- La otra solución es usar jzlib .
No encontré la otra respuesta para trabajar. Todavía se negó a vaciar porque el código nativo que usa GZIPOutputStream se mantiene en los datos.
Afortunadamente, descubrí que alguien ha implementado un FlushableGZIPOutputStream como parte del proyecto Apache Tomcat. Aquí está la parte mágica:
@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();
}
Puede encontrar toda la clase en este frasco (si usa Maven):
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-coyote</artifactId>
<version>7.0.8</version>
</dependency>
O simplemente ve y toma el código fuente FlushableGZIPOutputStream.java
Se lanza bajo la licencia Apache-2.0.
ID de error 4813885 maneja este problema. El comentario de "DamonHD", presentado el 9 de septiembre de 2006 (a mitad de camino del FlushableGZIPOutputStream
de FlushableGZIPOutputStream
) contiene un ejemplo de FlushableGZIPOutputStream
que construyó sobre Jazzlib''s net.sf.jazzlib.DeflaterOutputStream
.
Para referencia, aquí hay un extracto (reformateado):
/**
* 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();
}
// ...
}
Lo podrías encontrar útil.
No he intentado esto todavía, y este consejo no será útil hasta que tengamos Java 7 en la mano, pero la documentación para el GZIPOutputStream
flush()
de DeflaterOutputStream
heredada de DeflaterOutputStream
basa en el modo de descarga especificado en tiempo de construcción con syncFlush
Argumento (relacionado con Deflater#SYNC_FLUSH
) para decidir si se deben vaciar los datos pendientes para comprimirlos. Este argumento syncFlush
también es aceptado por GZIPOutputStream
en el momento de la construcción.
Parece que quieres usar Deflator#SYNC_FLUSH
o tal vez incluso Deflater#FULL_FLUSH
, pero, antes de desenterrar tan lejos, primero intenta trabajar con el argumento de dos argumentos o el argumento GZIPOutputStream
cuatro argumentos y pasa el true
argumento de syncFlush
. Eso activará el comportamiento de enrojecimiento que deseas.