理解压缩算法
这篇文章想聊聊软件中的压缩算法。
本文写给需要把大体积构建产物上传到服务器或存储、正在 zip、tar.gz、tar.zst 之间犹豫的开发者。读完之后,你会知道主流压缩格式在速度和压缩率上的差异出在哪里,并获得在 tar.gz 与 tar.zst 之间做选择的判断标准。
我曾负责改进一个公司内部项目的部署流程。它需要把体积很大的构建产物上传到 S3,我也由此切身体会到:构建目录的大小会直接影响上传时间与存储成本。于是一个问题自然出现了:怎样才能更高效地压缩并上传这些文件?
真正开始研究后,我发现压缩格式比想象中多得多:zip、gzip、zstd、bzip2、xz 等等。名字都很相似,但要找到一份清楚说明它们有何不同、分别适合什么场景的资料并不容易。(我原以为压缩都差不多,后来才发现世界很大,让文件变小的方法也很多。)
进入比较之前,先说明一个共同的根源。如今的主流压缩工具几乎都是 1977 年发表的 LZ77 的后代。LZ77 把已经处理过的一段数据(滑动窗口)当作字典,把重复出现的模式替换成“往回数若干个字符,再复制若干个字符”这样的简短引用。窗口越大,越能捕捉相距更远的重复,但计算量和内存开销也随之增加。ZIP 和 GZIP 使用的 DEFLATE,就是在此基础上加入霍夫曼编码(给出现频率高的符号分配较短比特的熵编码)。LZ77 与同辈的 LZ78 如何分道扬镳,另见 LZ77 与 LZ78 的区别。
评价压缩性能时通常要看两个维度:压缩率,也就是能缩小多少;以及压缩速度,也就是多久能完成。追求更高压缩率通常需要更多计算,压缩时间也会更长。现实中的压缩策略,核心就是在两者之间找到合适的平衡点。
有了这些基础,下面逐一比较几种常见格式。
ZIP
ZIP 是 Phil Katz 在 1989 年创造的文件格式,内部通常使用 DEFLATE 算法,也就是 LZ77 与 Huffman coding 的组合。重要的是,ZIP 本身不是“压缩算法”,而是“文件格式,也就是容器”。ZIP 这个容器里装着由 DEFLATE 等算法压缩的数据。
ZIP 的特点是分别压缩每个文件。这种逐个文件分别压缩的结构被称为非固实归档(Non-solid Archive)。与之相对,把所有文件连成一个流再一次性压缩的方式叫固实归档。得益于非固实结构,可以只取出归档中的某一个文件。另一方面,它无法利用文件之间重复的数据,所以压缩率可能低于后面要介绍的 tar.gz。
Windows、macOS、Linux 等大多数操作系统都能在不安装额外软件的情况下直接支持 ZIP,因此当跨平台兼容性很重要时,它是最稳妥的选择。
GZIP(GNU Zip)
GZIP 和 ZIP 一样,内部使用 DEFLATE 算法。既然算法相同,为什么还需要另一种格式?ZIP 同时承担把多个文件放进一个归档的容器职责,而 GZIP 专门用于压缩单个文件或单个数据流。
如果要用 GZIP 压缩多个文件或一个目录,需要先用 TAR 把它们打包成一个归档,再用 GZIP 压缩。这两步最终生成 .tar.gz 或 .tgz 文件。
GZIP 的文件结构由 RFC 1952 规定,非常简单:一个固定的 10 字节头部、可选的扩展头部(包含原文件名、注释等)、DEFLATE 压缩数据,以及包含 CRC-32 校验值和原始大小的8 字节尾部。CRC-32 用于验证解压数据是否与原数据一致。因此,GZIP 可以理解为包裹 DEFLATE 数据流的一层轻量封装。
DEFLATE 使用的滑动窗口最大为 32KB。这个大小会限制 GZIP 的压缩率,因为相隔 32KB 以上的模式无法互相引用。GZIP 还提供 1 到 9 的压缩级别。级别 1 速度快但压缩率较低,约为 60%;级别 9 较慢,但能达到约 75%。默认级别 6 是速度和压缩率之间的平衡点。
在 Unix/Linux 环境中,GZIP 长期被当作分发源代码、压缩日志和软件包的标准工具。它也仍然广泛用于通过 Content-Encoding: gzip 进行 HTTP 压缩,不过在这一领域正逐渐被 Brotli 取代。
ZSTD(Zstandard)
ZSTD 是 Meta(原 Facebook)的 Yann Collet 开发的压缩算法,于 2016 年开源。它最大的优势是:在保持与 GZIP 相近压缩率的同时,压缩和解压速度都快得多。
ZSTD 的内部结构大致分成三步。首先,LZ77 系的匹配查找器(Match Finder)从输入中寻找重复模式。然后把找到的字面量、匹配长度、偏移量等信息编码成序列。最后用熵编码压缩这些序列。它不用 GZIP 的霍夫曼编码,而是使用 FSE(Finite State Entropy)。FSE 是基于 ANS(Asymmetric Numeral Systems)理论的熵编码器。ANS 每处理一个符号就更新一个由单个整数表示的状态值,出现越频繁的符号输出的比特越少。它的目标是以接近霍夫曼编码的速度,达到接近算术编码(Arithmetic Coding)的压缩率。霍夫曼编码只能为每个符号分配整数个比特,而 FSE 可以表达相当于小数比特的概率,因此更接近理论最优值。(名字听起来很宏大,核心其实只是“用更聪明的方法,以更少比特表示同样的数据”。)
匹配查找器也会随压缩级别改变策略。低级别 1 至 4 使用简单哈希表快速搜索;中等级别 5 至 12 比较多个候选,并用 Lazy 策略选择更好的匹配;高级别 13 至 22 使用二叉树和动态规划寻找接近最优的匹配。级别可以从 1 精细调到 22,因此实时传输可以用低级别,归档则可以用高级别。
在 Silesia Corpus 基准测试中,ZSTD 默认级别 3 的压缩速度约为 300MB/s,解压速度约为 1,200MB/s。GZIP 默认级别 6 则只有约 34MB/s 和 380MB/s。ZSTD 压缩约快 8 倍、解压约快 3 倍,压缩率反而以 3.17 高于 GZIP 的 3.09。 这些数字最直观地说明了 ZSTD 如何改善传统权衡。
它的生态采用也在快速扩大。ZSTD 被用于 Linux 内核模块压缩和文件系统透明压缩,Arch Linux、Fedora、Debian、Ubuntu 等主要发行版也把它采用为软件包压缩的默认格式。从 2025 年 2 月发布的 v1.5.7 开始,最多使用 4 个线程的多线程压缩默认启用,与单线程 GZIP 的实际速度差进一步拉大。AWS 也曾表示,把内部服务从 gzip 切换到 zstd 后,S3 存储减少了约 30%。
BZIP2
BZIP2 通过多阶段转换来压缩数据,核心流程如下。
- RLE(Run-Length Encoding):减少原始数据中连续的重复
- BWT(Burrows-Wheeler Transform):重新排列数据,使其更容易压缩
- MTF(Move-to-Front Transform):把 BWT 输出转换成数字序列
- RLE:再次减少 MTF 结果中的重复
- Huffman Coding:最后按频率进行编码
BZIP2 的压缩率高于 GZIP,但压缩和解压都较慢。它长期用于压缩率重要而速度不那么重要的归档场景。
不过,它最后一次发布是 2019 年的 v1.0.8,目前没有活跃开发。越来越多基准测试表明 ZSTD 在压缩率和速度两方面都胜过 BZIP2,因此新项目更常选择 ZSTD。
XZ
XZ 是使用 LZMA2 的压缩格式。Igor Pavlov 开发的 LZMA(Lempel-Ziv-Markov chain Algorithm)把基于 LZ77 的字典压缩与范围编码(Range Encoding)结合起来。LZMA2 与其说是“改进版 LZMA”,不如说更接近包裹 LZMA 数据流的容器格式。它主要增加了多线程压缩与解压,以及对不可压缩数据的高效处理。
在本文介绍的格式中,XZ 拥有最高的压缩率。代价是压缩速度很慢,内存占用也很大。它适合把节省存储空间放在最高优先级的归档场景。
但在 2024 年 3 月,XZ 的核心库 xz-utils 被发现存在后门,引发严重的供应链安全事件 CVE-2024-3094。攻击者通过长达两年的社会工程获得维护者权限,该漏洞获得最高的 CVSS 10.0 分。主要发行版立刻回滚到安全版本,但这起事件给开源供应链安全敲响了警钟。(XZ 本身的技术价值依然存在,不过选择工具时也值得了解这段背景。)
TAR
TAR(Tape Archive)本身不是压缩算法,而是一种把多个文件和目录组合成一个归档文件的工具与格式。顾名思义,它最初是为磁带备份设计的。磁带是一种顺序介质,因此连续拼接数据是很自然的结构。
TAR 的内部结构出乎意料地简单。所有数据都按512 字节块处理。每个文件前有一个 512 字节头部,其中记录文件名(最多 100 字节)、文件模式、所有者 UID/GID、大小、修改时间和校验值等元数据。文件数据紧跟在头部之后,并填充到 512 字节的整数倍。归档结尾用两个 512 字节的全零块表示。现代大多数 TAR 实现遵循 POSIX 定义的 UStar(Unix Standard TAR)格式,支持最长 256 字节的文件名和更多元数据字段。
关键在于,TAR 会原样保留权限、所有权、时间戳和符号链接等Unix 文件系统元数据。ZIP 有时无法完整保留这些 Unix 特有信息,因此在服务器部署中 TAR 往往更合适。
TAR 本身不会缩小数据。因为头部和填充,它反而比原始数据略大。真正的压缩需要与 GZIP、BZIP2、XZ、ZSTD 等工具结合完成。这就是 .tar.gz、.tar.bz2、.tar.xz、.tar.zst 等扩展名存在的原因。TAR 负责“打包”,压缩工具负责“缩小”,这是 Unix “做好一件事”哲学的典型例子。
TAR 是 Unix/Linux 环境的标准归档方式,而 Windows 可能需要 7-Zip 等额外软件。
也了解一下 Brotli
前端开发者也应该了解 Brotli。它是 Google 开发的压缩算法,并于 2015 年作为 HTTP 数据流压缩规范 Content-Encoding: br 标准化。
所有主流浏览器都在 HTTPS 环境中支持 Brotli,全球支持率超过 96%。它通常比 GZIP 多压缩约 15% 至 25%,尤其适合 JavaScript、CSS、HTML 等文本型静态文件。Cloudflare 等主要 CDN 已将它作为默认压缩方式,现代 Web 优化的常见做法可以概括为“优先 Brotli,回退到 GZIP”。
如果构建产物上传到 S3 并通过 CDN 提供服务,预先用 Brotli 压缩静态文件可以显著减少网络传输。(当时还没有足够的项目资料支持立刻引入它,但作为开发者,了解并在以后重新评估这项选择仍有价值。)
为什么 tar.gz 的压缩率高于 ZIP?
原因在于固实归档(Solid Archive)与非固实归档(Non-solid Archive)的区别。
tar.gz 会先用 TAR 把所有文件组合成一段连续数据流,再由 GZIP 一次压缩整个数据流。这样就能识别和利用跨文件的重复数据,这就是固实归档。如果构建目录里有几十个结构相似的 JavaScript bundle,文件 A 中出现的模式在文件 B 再次出现时就可以引用。由于不必为每个压缩流分别记录头部、校验值和目录(Table of Contents),元数据开销也会减少。
ZIP 属于非固实方式,会独立压缩每个文件,因此无法利用文件之间的冗余。即使文件 A 和 B 包含相同代码块,两条独立的 DEFLATE 数据流也不知道对方存在。这就是 tar.gz 通常比 ZIP 获得高 5% 至 15% 压缩率的原因。构建产物中结构相似的文件越多,差距往往越大。
固实归档也有明显缺点。
- 如果只想取出某一个文件,可能仍要先解压它之前的全部数据。所有文件处于同一数据流,无法直接跳到中间位置。ZIP 可以随机访问单个文件,因此在经常提取特定文件的场景里可能更合适。
- 如果归档的一部分损坏,损坏点之后的所有数据都可能无法恢复。非固实格式有时只会丢失受损文件,其余内容仍能保留。
2024 年选择了 tar.gz,现在会怎么选?
本节是 2026 年补充的。当时选择 tar.gz 是因为兼容性和稳定性。产物上传 S3 后需要在多种环境中解压,因此几乎随处可用的 tar.gz 是安全选择。
但如果今天再次遇到同样的情况,我会认真考虑 tar.zst(TAR + ZSTD)。回想前面的基准数字。
GZIP 默认级别的压缩速度为 34MB/s,ZSTD 默认级别则为 300MB/s。对一个 2GB 构建目录做简单计算,GZIP 约需 60 秒,ZSTD 约需 7 秒。从 ZSTD v1.5.7 开始默认启用的多线程能力最多使用 4 个线程,实际体感时间会更短。在 CI/CD 流水线中,这个差距会在每次部署时不断累积,不容忽视。
# tar.zst 생성 (멀티스레드 자동 활용)
tar --zstd -cf archive.tar.zst directory/
# 또는 압축 레벨 지정 (-T0은 사용 가능한 모든 코어 활용)
tar -cf archive.tar.zst -I 'zstd -3 -T0' directory/ZSTD 的压缩率也与 GZIP 相当甚至更高,因此“为了速度而牺牲压缩率”的权衡实际上已经不存在。它既更快,结果也更小。
不过,仍然必须确认接收环境能够解压 zstd。主要 Linux 发行版已经包含它,macOS 也可以通过 Homebrew 的 brew install zstd 轻松安装。老旧系统或最小化安装环境可能需要额外安装,因此应提前检查团队使用的所有环境。如果兼容性是第一优先级,tar.gz 仍然是最稳妥的通用选择。
一览对比
| 格式 | 算法 | 压缩率 | 速度 | 主要特点 |
|---|---|---|---|---|
| ZIP | DEFLATE | 中等 | 快 | 跨平台、非固实 |
| GZIP | DEFLATE | 中等 | 快 | 单一数据流、常与 TAR 结合 |
| ZSTD | Zstandard | 高 | 非常快 | 级别可调、现代标准 |
| BZIP2 | BWT+MTF+Huffman | 高 | 慢 | 开发基本停滞 |
| XZ | LZMA2 | 非常高 | 非常慢 | 最高压缩率、需注意安全背景 |
| Brotli | Brotli | 高 | 中等 | 专为 Web 优化 |
结语
深入研究压缩之前,我坦率地想过:“直接用 zip 打包不就行了吗?”真正处理超过 2GB 的构建目录后,我才直观感受到,算法选择会显著改变上传时间和成本。
每种压缩格式都有自己的设计哲学和权衡:ZIP 的兼容性、GZIP 的通用性、ZSTD 的速度、XZ 的压缩率。不存在适用于所有情况的“最佳”方案,正确选择取决于项目约束。
理解那些平时不假思索就使用的工具,能帮助我们在下一次遇到类似问题时作出更好的判断。希望这篇文章能给需要选择压缩算法的人提供一点参考。