Base64 为什么会增加 33% 体积
2026-09-30 · 1191 词
Base64 会把数据变大。不是稍微大一点 —— 是每次大三分之一,没有例外,也没有可调的开关。这个数字不是某个实现的怪癖,它直接从编码规则里推出来;搞清楚它是怎么来的,内联与否的决策就会容易得多。
那三分之一从哪来
Base64 的存在,是为了只用"能在文本传输中存活"的字符来表示任意字节。它的字母表有 64 个符号,也就是每个字符 6 位信息;而字节是 8 位。所以编码器一次读 3 个字节(24 位),写出 4 个字符(4 × 6 = 24 位)。
三个字节进,四个字符出:
3 字节 → 4 字符 4 / 3 = 1.333...
故事就这么多。300,000 字节的图片变成 400,000 个字符的字符串。开销精确地是 33.3%,而且只作用于载荷本身 —— data:image/png;base64, 这个前缀另加 22 个字符,对小资源有意义,对大资源无关紧要。
唯一能调节的是填充。如果输入长度不是 3 的倍数,最后一组会用不携带信息的 = 补齐,所以精确长度是 4 × ceil(n / 3):
- 1 字节 → 4 字符,开销 +300%
- 3 字节 → 4 字符,开销 +33%
- 100 字节 → 136 字符,开销 +36%
- 1,000 字节 → 1,336 字符,开销 +33.6%
- 1,000,000 字节 → 1,333,336 字符,开销 +33.3%
小输入那几行,正是"为了省体积而内联小资源"站不住脚的理由:1 字节的文件变成 4 个字符加 22 个字符的前缀,前缀才是主角。
为什么 gzip 救不了你
对文本,gzip 通常能压掉 60–80%,所以"33% 无所谓"这句话有时会被拿出来说。这个推理对 JSON 和 HTML 成立,对图片完全失效。
压缩靠的是找冗余。JPEG、PNG、WebP、AVIF 已经把冗余基本去干净了 —— 那正是这些格式的工作 —— 所以它们的字节在压缩器看来接近随机。Base64 把这些不可压缩的字节编码进一个只有 64 个符号的受限字母表,并没有引入任何压缩器可利用的冗余。结果是两头都输:数据大了 33%,而且依然压不动。
一条命令就能量出来:
# 一张真实图片,本身已经压缩过
base64 -w0 photo.jpg > photo.b64
ls -l photo.jpg photo.b64
gzip -9 -c photo.b64 | wc -c # 比 photo.b64 大不了多少,也小不了多少
文本载荷的情况不一样。如果你 Base64 编码的是一段文本(JSON、XML、日志),gzip 对编码后的形态能压得很好,有时甚至比压原文更好,因为文本的 Base64 里仍然有可识别的结构。所以"gzip 能不能消掉那 33%"有两个答案,完全取决于载荷原本是否可压缩。
成本不只是字节
把样式表或 HTML 文档撑大,有三项成本不会出现在文件大小对比里:
- 阻塞渲染。 放在
<style>块或外链 CSS 里的 data URI 是那个文件的一部分。浏览器必须先下载再解析,解析完才能绘制。而一张外链图片的请求不在这条关键路径上。 - 无法独立缓存。 内联了二十个图标的样式表,改一个字节,每个访客就要重新下载全部二十个。作为独立文件时,它们各有各的缓存条目和校验器。
- 解析与内存。 这些字节必须先被解码、以字符串形式持有,才能变成图片。样式表里一段 400 KB 的字符串,就是解析器必须走完的 400 KB 文本,而且它会一直留在 CSSOM 里。
这些都不致命。但只要你只比较文件大小,它们全都是隐形的。
盈亏平衡点到底在哪
不存在通用阈值,但决策区间足够窄,可以写成几条经验规则:
- 约 2 KB 以下:内联。 省掉一次请求(HTTP/1.1 下仅响应头就常常 300–800 字节,HTTP/2 下更少)带来的收益可能超过那 33%。单色图标或很小的 SVG 是典型场景。
- 2–10 KB:取决于缓存。 favicon 或永不变化的 Logo 可以安全内联;每周都要更新的东西不该内联;在十个页面用到的资源应该是文件,这样只下载一次。
- 约 10 KB 以上:用文件。 此时开销已经要以 KB 计,而且你并没有省下任何请求,却白白放弃了缓存。
- 约 1 MB 以上:永远不要内联进文档。 除了体积惩罚,你还进入了"某些工具会截断超长属性、某些解析器会变慢"的地带。
对图片来说,比"这段 Base64 有多大"更好的问题是:它能不能是 SVG? 一个矢量 Logo 只有几百字节的路径数据,编码后不到 1 KB,而且与分辨率无关。内联它显然是赚的,也是这项技术赢得好名声的场景。
在你自己站点上量一量
两个数字就能说明内联是否有收益:包含 data URI 的文档的传输大小,以及用外链资源的等价页面的传输大小。如果内联版本大 30%,那就是大 30% —— 那 33% 不可能被网络层拿回来。
这个测量有一种典型的做错方式:拿图片的未压缩大小去比文档的压缩后大小。要同口径比较,并且记住 data URI 的字节是在一个 gzip 只能部分压缩的响应里传输的。我们的 PNG 转 Base64 和 JPG 转 Base64 页面会在转换时报告输出长度与开销,通常足够让你立刻看出:一张 900 KB 的照片不该被内联成背景图。
对于你确实需要那个字符串的场景 —— 关键 CSS 里的小图标、无法引用外部文件的邮件模板、单文件 HTML 导出、测试里的 JSON 夹具 —— 这份开销是"自包含"的合理代价,而知道确切数字,能让它成为一个有意的选择而不是意外。如果你比较的是"在内存里持有图片"的两种方式而不是写进文档,data URI 与 blob URL 讲的是这个权衡的另一半。