如何让GZIP压缩输出保持恒定的流速度?
问题
我尝试对一个长期运行进程的输出进行流式压缩,该进程的输出速度恒定,但GZIP压缩后的输出速度却不稳定,不清楚原因。
示例代码如下:
import java.io.ByteArrayOutputStream; import java.util.Random; import java.util.zip.GZIPOutputStream; public class Snippet { public static void main(final String[] args) throws Exception { long startTime = System.currentTimeMillis(); ByteArrayOutputStream verboseBAOS = new ByteArrayOutputStream() { @Override public synchronized void write(final byte[] b, final int off, final int len) { long milestone = System.currentTimeMillis() - startTime; System.out.println(" Wrote (" + len + " bytes) at ms: " + milestone); super.write(b, off, len); } }; GZIPOutputStream bigChunkOs = new GZIPOutputStream(verboseBAOS, 2048); new Thread(() -> { try { for (int i = 0; i < 20480; i++) { bigChunkOs.write(new Random().nextInt('A', 'z')); if (i % 100 == 0) { System.out.print("."); Thread.sleep(1); } } bigChunkOs.close(); } catch (Exception e) { } }).start(); } }
运行输出如下:
Wrote (10 bytes) at ms: 1 .............................................................................................................................................................................. Wrote (2048 bytes) at ms: 207 Wrote (2048 bytes) at ms: 207 Wrote (2048 bytes) at ms: 207 Wrote (2048 bytes) at ms: 207 Wrote (2048 bytes) at ms: 207 Wrote (2048 bytes) at ms: 207 Wrote (367 bytes) at ms: 207 ............................... Wrote (2048 bytes) at ms: 243 Wrote (483 bytes) at ms: 243
输出分为三个阶段:
- 头信息(约1ms)
- 第一个块(约207ms)
- 第二个块(约243ms)
期望输出块更均衡,消除约203ms的间隔,该如何解决?
原因分析
GZIPOutputStream默认会延迟输出压缩数据,必须等内部缓冲区填满,或者调用flush()/close()才会输出压缩结果。你设置了2048字节的缓冲区,但单字节写入的效率极低,加上Thread.sleep(1)的间隔,导致数据积累到缓冲区满需要很长时间,这就出现了长时间无输出、然后一次性批量输出的情况。
另外,Deflate压缩算法本身会积累数据形成压缩块,只有当块大小达标或缓冲区满时才会输出,这也加剧了输出的不平稳。
解决方案
要实现平稳输出,可从以下两点调整:
1. 主动调用flush()强制输出
在每次写入一批数据后,调用bigChunkOs.flush(),让GZIPOutputStream立即输出当前已压缩的数据,不用等缓冲区填满。
修改后的写入逻辑:
for (int i = 0; i < 20480; i++) { bigChunkOs.write(new Random().nextInt('A', 'z')); if (i % 100 == 0) { System.out.print("."); // 强制输出当前压缩数据 bigChunkOs.flush(); Thread.sleep(1); } }
2. 优化写入方式,减少单字节写入开销
单字节写入会频繁触发内部操作,效率低下。改成批量写入数据,配合flush能让输出更平稳:
Random random = new Random(); byte[] batchBuffer = new byte[100]; for (int i = 0; i < 20480; i += 100) { int writeSize = Math.min(100, 20480 - i); // 批量生成随机字节 for (int j = 0; j < writeSize; j++) { batchBuffer[j] = (byte) random.nextInt('A', 'z'); } bigChunkOs.write(batchBuffer, 0, writeSize); System.out.print("."); bigChunkOs.flush(); Thread.sleep(1); }
注意事项
频繁调用flush()会让GZIP生成更多独立的压缩块,每个块的压缩上下文会被重置,整体压缩率会略有下降。如果优先保证输出平稳,这个代价是可接受的;如果需要高压缩率,则需要权衡输出间隔和压缩率的平衡。
内容的提问来源于stack exchange,提问作者Grim
相关产品推荐
相关产品推荐

