GZIPOutputStream、Deflater 这些 API 用起来简单,边界却不少:三种格式的头尾、级别的收益、坏数据的各种异常,以及解压时的内存风险。这篇每一项都真实运行并量出数字。
原始数据:JSON 行日志 335563 字节(4000 行) zlib 格式(Deflater 默认):48059 字节,前 2 字节 78 9c;raw deflate(nowrap=true):48053 字节;gzip(GZIPOutputStream):48071 字节,前 10 字节 1f 8b 08 00 00 00 00 00 00 ff 三者的差:zlib − raw = 6(2 字节头 + 4 字节 Adler-32),gzip − raw = 18(10 字节头 + 8 字节 CRC-32 与长度) nowrap=true 的 Inflater 还原:335563 字节,是否等于原文 true,finished = true 用 zlib 模式(nowrap=false)去解 raw deflate → DataFormatException: incorrect header check 命令行 gzip -6 -c data.json:48081 字节(Java 的 gzip 是 48071 字节);命令行输出头 10 字节 1f 8b 08 08 31 c5 c0 6a 00 03:标志位 08(08 = 带原文件名,多出 10 字节)、OS 字段 03(03 = Unix);Java 写的标志位是 00、OS 字段 ff(未知) 命令行 gzip -dc 解压 Java 生成的 gz:335563 字节,与原文逐字节比较 cmp 的返回码 0(0 = 一致)
每格:压缩后字节数/单次压缩耗时(预热后 20 次平均) JSON 行日志(335563 字节) L0:335619字节/0.26ms L1:62382字节/1.06ms L6:48059字节/2.88ms L9:45787字节/14.41ms 随机字节(335563 字节) L0:335619字节/0.21ms L1:335674字节/5.12ms L6:335674字节/5.23ms L9:335674字节/5.25ms 已压缩的数据再压一次:gzip(原文)=48071 字节,再 gzip 一次 = 48104 字节(更大) 8 MiB 全零字节 gzip 后 8175 字节(压缩比约 1026:1) Deflater.DEFAULT_COMPRESSION = -1(-1,实际用 6),BEST_SPEED = 1,BEST_COMPRESSION = 9 预设字典:压缩前 300 字节(首 3 行)→ 无字典 143 字节,带字典 96 字节;解压端需要同一个字典(Inflater.needsDictionary)
gzip 截断(去掉最后 100 字节)→ EOFException: Unexpected end of ZLIB input stream gzip 中间一个字节被改 → ZipException: Corrupt GZIP trailer gzip 的 CRC 字段被改 → ZipException: Corrupt GZIP trailer 两个 gzip 成员首尾拼接:GZIPInputStream 读出 671126 字节(单个成员 335563 字节)——自动读后续成员 gzip 后面跟 20 个垃圾字节:读出 335563 字节,没有报错 不是 gzip 的数据 → ZipException: Not in GZIP format 空输入 → EOFException: null zlib 流截断 → EOFException: Unexpected end of ZLIB input stream
256 MiB 全零 gzip 后只有 254 KiB(压缩比约 1028:1) 读取时设置 10 MiB 上限:读到 10 MiB 时停止(超限 = true),这时只消耗了不到 254 KiB 的输入 无上限流式读完:256 MiB,耗时 97 ms(这次只统计字节数、没有保存) -Xmx512m 下 readAllBytes 读 256 MiB → OutOfMemoryError: Java heap space GZIPOutputStream(syncFlush = false) 写一行并 flush() 之后,管道另一端已经可读的字节数 = 10(10 字节正好是 gzip 头,压缩数据还留在内部缓冲里) GZIPOutputStream(syncFlush = true) 写一行并 flush() 之后,管道另一端已经可读的字节数 = 27(对方能立即解出这一行)