Принудительная очистка на 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()