算法 — 历史、性能、当下定位
lha 携带的是 LZHUF:1990
年代的 LZ77 + 动态 Huffman,为「1 MiB 内存的 286 上的快速
解压」而设计,写在 SIMD 还没出现的年代。这一页说的是这
个算法、在 2026 年它付出的代价,以及为什么一个外部
项目的单一静态二进制仍然会以它为底座。
1. 算法
lha 家族里的每一种压缩方式(-lh0- 到
-lh7-,外加 -lhd- 目录头部)都是
同一个两步结构:
- LZ77 风格的字符串匹配。在输入里查找重复的字节
序列,并发出形如
(distance, length)的回 引用,代替重复字节。所有 LZARI / LZHUF / ZIP / gzip / zstd / lz4 / 7-zip 里的「LZ」都是这一个想法,源头是 Lempel & Ziv 1977 年的论文。 - 熵编码器,作用在字面量和回引用上。这是各家不同
的部分:
-lh1-— LZARI: LZ77 + 算术编码(Okumura,1988)。-lh5-— LZHUF: LZ77 + 动态 Huffman(Tagawa,1990)。默认方式。-lh4-、-lh6-、-lh7-、-lzs-— 熵后端的实验性改进,或为兼容而保留的未使用编码。
静态 vs 动态 Huffman
LZHUF 用的是静态 Huffman 表,每个块重算一次,输入 里每几 KB 重新派生一次。这个设计抉择决定了整套取舍:
- 好处。编码器不必把 Huffman 表随比特流一起发出。 解码器已知每块的表,在一个紧凑的内层循环里解码,不必 在每个块开头加载新表。
- 代价。当局部统计量变化(头部块 —> 结构化 文本主体 —> 二进制块)时,静态表是局部次优的。 动态 Huffman(deflate 的默认,也就是 gzip 用的) 把表跟数据一起携带并自适应。这就是基准 里 ~5 % 压缩率差距的来源。
位串行解码循环(以及为什么它慢)
LZHUF 的解码循环是逐比特读入比特流,每次通过 一个小型 2× 表查找下一个符号。这从根本上就是串行的: 比特 n+1 必须在比特 n 查表完成后才能读。
ANSI C 源码就是直白的 1995-2003 年代码,没有 SIMD intrinsic:
$ grep -nEi '(mmx|sse|avx|simd|__m128i|__builtin_ia32)' upstream/lha/src/*.c
$ # 零命中
把 LZ77 的 length-distance 拷贝向量化,可以拿到大约 1.5–2× 的解压速度;位串行的 Huffman 循环就是天花板。 要在解压上再快,必须换算法,不能再微优化 LZHUF(更长 的版本见 基准页)。
2. 历史 — 世系
首页的历史章节对 1988-2022 这一段弧线讲得很详细;紧凑版:
- 1977 — LZ77。Abraham Lempel 与 Jacob Ziv 在 IEEE Transactions on Information Theory 发表 A Universal Algorithm for Sequential Data Compression。滑动窗口 + 回引用这一思路,定义了 之后每一种现代归档器。
- 1988 — LZARI。奥村晴彦发表对 CPU 友好的 LZ77 + 算术编码变体。LHA 借用的、面向 286 的算术 编码器。
- 1988–1989 — LHarc。吉崎英明 (Y.Tagawa)把 LZARI 包成 MS-DOS 上的归档格式。日本 第一种广泛使用的归档格式。
- 1990 — LZHUF,本项目分发的格式。Tagawa 把
LZARI 改进为 LZ77 + 动态 Huffman,把格式从 LHarc 改名
为 LHA。压缩方式后缀的约定(归档文件名里的
-lh5-)就此诞生,并沿用至今。 - 1991–1993 — LHA for UNIX。大木优移植 到 UNIX;绵贯和彦重写代码库。这份 canonical C 源码, 30 年来一直被 vendored。
- 1993 — PKZIP 2.0 + Info-ZIP在西方 PC 上 取代 LHA。
- 1994–2010 —
.lzh在日本仍然火。 主要站点用 LZH 分发;Windows 自解压大量使用 LHA;2ch 风格的电子公告板交换 LZH 一直延续到 2000 年代后期。.lzh是日本 2000 年代初之前主流的分发 格式。 - 2022 — autoconf 复活。GitHub 用户
jca02266
接手沉寂的代码树,加入现代 autotools 构建,打出
LHa for UNIX 1.14i-ac20220213 标签。这就是本仓库
在提交
86094cb处 vendored 的来源。 - 2026 — ljh-sh/lha。Vendors
jca02266/lha@86094cb;为每个主流平台产 出单一静态二进制。当下还存在的理由在 §4。
3. 性能 — 三条轴
基准数据来自 1.83 MiB 的 kenlm 源 码语料,每个 cell 跑 5 次,取中位数 ± 样本标准差。要点:
| 工具 | 级别 | 压缩 | 解压 | 压缩率 |
|---|---|---|---|---|
| lha | -lh5-(默认) |
~50 ms | ~40 ms | 29.93 % |
| zip | -6(默认) |
~62 ms | ~3 ms | 31.01 % |
| tar(不压缩) | — | ~80 ms | ~82 ms | 149.55 % |
| tar.gz | -6(默认) |
~210 ms | ~5 ms | 24.93 % |
三条值得记的观察
- 压缩速度。
lha排在zip(最快,领先 ~22 %)和tar.gz(最慢,慢 ~4×)之间。对于「压缩后 留存」类负载(构建产物留存、镜像站),lha是非平凡压缩器里最便宜的,仍然比裸流传得快 ~3×。 - 解压速度。
zip和tar.gz解压在个位数毫秒;lha要 ~40 ms。位串行 Huffman 循环就是天花板。绝对时间仍然很小 — 远 低于任何 CDN 的网络 RTT 噪声。在 CDN 上从镜像重新 解压 100 次的工作负载,被 HTTP 延迟支配,而不是算法 选择。 - 压缩率。
lha比deflate高 ~5 个百分点(1.8 MiB 源码上多 ~91 KB)。这是 1990 年的有意取舍:调优目标是 286 上解压快,而不是 2020 年 工作站上压得最紧。
哪里输给现代算法 — 哪里不输
- ✗ 压缩率上,
deflate和zstd赢 5–15 个百分点。 - ✗ 解压速度上,
zstd比lha快 ~5–10×;lz4再快 ~3–5×。 - ✓ 静态二进制体积 + 零依赖分发上,
lha完胜: ~150 KB,在任何有内核的地方都能跑。 - ✓ 「我手上有个 1994 年游戏的归档,其它都打不开」上,
lha默认就赢 — 因为只有它能打开。 - ✓ 长期归档稳定性上,
lha赢在它的 35 年格 式规范今天还在被实现,还在保持读兼容。
基准页的「深入」章节讲算法 内部原因。短版本:lha 是 1990 年的设计,到今天没变过, 速度天花板就是算法本身,不是实现。
4. 当下定位 — 为什么这个项目在 2026 年还存在
被 vendored 的 jca02266/lha@86094cb 源码,是
1995-2003 年代的 ANSI C 代码库,2022 年只经历过一次现代
化(autotools)。ljh-sh/lha 从那里出发只做一件事:为
每个平台产出一个 lha 的单一静态二进制,让
用户不需要编译器。
2026 年这个命题有三类具体需求场景:
4.1 老 .lzh 的解压
最直观的情况:某人手上有 1994 年的游戏补丁、2ch 风格的
BBS 历史附件、或 2000 年代的日本开源归档。现代工具都打不
开 .lzh。装上 x eget use lha,
两秒钟就到 ~/.local/bin,再
lha x foo.lzh 就能解。少了这个二进制,用户
只能装编译器、找 1990 年代的 autoconf 补丁、然后祈祷
他们的 libc 还没把这构建搞坏。
4.2 单文件静态二进制用于 CI 产物 / CDN 分发
lha 从任何源码树产出一个 .lzh
归档,耗时 ~50–80 ms,主机上零系统依赖。对于 CI 产
物发布者(任何把构建结果打包下载的人来说),意思是:
- 不用把
libz、libcurl、libiconv、libssl拖进精简 容器。 - 一个 ~150 KB 的二进制,签名、分发。
- 解压路径在 Windows、macOS、Linux、BSD 和任何嵌入
式 Linux 上都成立 — 因为二进制从
~/.local/bin跑,不需要安装到系统。
这就是 ljh-sh 在各项目里都采用的「静态二进制分发约定」 (kenlm、upxz、现在的 lha)。具体流水线可以看 构建审计。
4.3 x-cmd 的 zuz 模块 — 直接动机
这是本项目以静态构建形式分发的根本理由。
x-cmd 的
zuz 模块对归档文件做格式自动识别:按扩展名
选工具,透明地解压或压缩。它支持 tar、
tar.gz、tar.bz2、tar.xz、
zip、7z、rar、
lha 等。但它有一条架构铁律:
绝不内嵌格式专用的工具。始终调用系统已安装的 二进制。
问题落在 x-cmd
issue #131(2024 年 12 月):某个用户希望 zuz
支持 LHA/LZH,但 x-cmd 当前支持的所有便携式包管理器都不
自带 lha。讨论线索如下:
- 2024 年 12 月:维护者(edwinjhlee)指出 v0.5.1 已经
提供了基于 7z 的解压回退(只要用户装了
lha或lhasa就能跑)。 - 2025 年 1 月:维护者让用户用
x install lha作为权宜路径,直到出现打包的lha模块。 - 2026 年 7 月:维护者
收尾:
zuz将惰性下载本仓库的发布产物, 这样x uz foo.lha在任何预先没装lha的系统上都能跑。
也就是说,zuz 用户的主安装路径是:
x uz foo.lha
# zuz 检测到 .lha,发现 PATH 里没 lha,
# 回退到:eget ljh-sh/lha --to ~/.local/bin/lha
# 然后:~/.local/bin/lha x foo.lha
不是 Homebrew。不是 apt。不是各发行版的包。一个默认 的静态安装,不需要特权安装、不需要 tap、不需要构建 链、在 x-cmd 支持的每个平台上都能用。本仓库存在的全部 理由,就是为它提供这一份「默认静态安装能力」。
如果你是 x-cmd 用户,在一台没装 lha 的系统
上 x uz foo.lha,你就是在用这个二进制。
如果你从来没用过 x-cmd,只是要打开一个
.lzh,你也在用这个二进制 — 同一份产物、
同一个发布、同一个被 vendored 的源码。
4.4 这个项目「不是」什么
老实讲清楚边界:
- 不是算法升级。没有 tANS 移植,没有 LZMA-lha 变体,没有面向 LZH 的 tANS 格式。Vendored 的 1.14i 保持原样。
- 不是
tar.gz、zip、zstd的替代品。基准 说得很清楚,何时该选哪个。 - 不是LZH 卷土重来的信号。这个二进制存在的唯
一理由是格式本身的长寿;如果你的工作流是 2010 年之
后开始的,你多半没有
.lzh文件。
5. 总结
想要一条经验法则:
| 如果你关心… | 就选 |
|---|---|
打开一个 1994 年的 .lzh |
lha(本项目的二进制) |
| 闭盒系统上的单一静态二进制 | lha(~150 KB,零依赖) |
类似 zuz 的格式自动识别 |
lha(惰性从 ljh-sh/lha 拉) |
| 长尾 CDN 上最小的产物 | tar.gz -9 或 zstd |
| 微秒级解压 | lz4 或 zstd |
这一页存在的理由是:「这个二进制是什么」和「为什么它 存在」值得集中在一个地方,而不是被历史、 基准、构建审 计、FAQ 各页瓜分。