一个字节 8 bit 是硬件事实,可打印 ASCII 不到一百个是传输现实,两边对不上。Base64 就是夹在这两个约束中间的那个整数解。这篇把这道算术做完:为什么不是一字节对一个字符,为什么 6 bit 一组,为什么 3 个字节刚好变成 4 个字符,为什么干脆不用 94 个可打印字符,以及换成 32、85 会分别付出什么代价。
读完你应该能回答"如果字符表只有 63 个会怎样",也能自己算出任意长度数据的 Base64 有多长。
一、一字节对一个字符,为什么走不通
8 bit 有 256 种取值。ASCII 只覆盖 0 到 127,其中 0x00 到 0x1F 加 0x7F 是控制字符,会被设备当指令处理;剩下能安全传输的只有可打印那一段(0x21 到 0x7E,共 94 个,把空格算进去也才 95)。一字节对一个字符这条路,最多只能覆盖一半取值,另外 128 种根本无处安放。
出路是反过来想:不要一一对应,把字节拆开重装。1 个字节 8 bit,用 2 个字符各带 6 bit 就有 12 bit 容量,装得下。但每存 2 bit 真实信息要付一个字符,短数据的膨胀会很难看。
二、6 与 8 的最小公倍数是 24
分组编码要能连续切分,组的边界得反复和字节边界对齐,所以字符集携带的位数必须和 8 有干净的最小公倍数。把几个候选都摆出来:
- 4 bit、16 个字符:最小公倍数 8,一字节正好换两个字符,长度翻倍。这就是十六进制(Base16)。
- 5 bit、32 个字符:最小公倍数 40,5 字节换 8 个字符,涨 60%。这是 Base32。
- 6 bit、64 个字符:最小公倍数 24,3 字节换 4 个字符,涨三分之一。这是 Base64。
- 7 bit、128 个字符:最小公倍数 56,7 字节换 8 个字符,只涨 14%,可惜 128 个安全字符凑不出来——可打印 ASCII 只有 94 个,光这一条就把它否了。
64 是装得进安全字符集的最大 2 的幂,而 24 又是相当短的循环周期。标题那个问题的答案就在这里:不是"64 个字符够用",而是"64 是能整除对齐的上限"。
三、膨胀率的算术
带填充的标准输出,长度 = ⌈原始字节数 ÷ 3⌉ × 4。几个可以直接复核的数字:
- 3 字节 → 4 字符;1 字节 → 4 字符;2 字节 → 4 字符。短数据最吃亏,1 字节要涨 300%。
- 100 字节 → 136 字符,涨约 36%。
- 1024 字节 → 1368 字符,涨约 33.6%。
- 1 MB(1048576 字节)→ 1398104 字符。
同样 1024 字节,Hex 要 2048 个字符,Base32 要 1640 个,Base85 只要 1280 个。用 Base64 转 Hex 把同一段数据两头一比,这个长度差很直观;再用 Base64 在线编码解码 输入 1、2、3、4 个字符各编一次,填充符和长度规律当场就能看见。
四、为什么不干脆用 94 个可打印字符
字符表还有富余(94 减 64 还剩 30 个),为什么不做一张 94 的表?两个理由。
一是 94 不是 2 的幂。log2(94) 约等于 6.55 bit,位边界对不齐,编码要么变成长度不齐的前缀方案,要么改成按大数做除法,实现和调试成本都比查一张 64 的表高一截。
二是可打印不等于可传输。空格、双引号、反斜杠、百分号、加号、斜杠在邮件头、URL、HTML、Shell、正则、JSON 里各有各的语法含义,真用满 94 个字符,等于把转义问题永久留在链路上。Base64 只挑 64 个,其中 62 个字母数字在任何语法里都不特殊,剩下 + 和 / 两个后来在 URL 场景还得换成 - 和 _,也就是 RFC 4648 里的 base64url。
顺带一个实用结论:一段字符串里出现 - 或 _,八成是 URL 安全变体;出现 + 或 /,是标准表。拿不准就两种都试着解一次,看哪一种能得到可读结果。
五、选错了会怎样:32、85、58 的取舍
Base32 的价值不在省空间,而在人能看懂。它只用 A-Z 加 2-7,把 0 与 O、1 与 I、8 与 B、9 与 G 这类容易看错的形状都请出了表,适合语音报读和手抄。代价是同样数据比 Base64 多两成字符(涨 60% 对涨 33%)。
Base85 更省:5 个字符装 4 个字节,涨 25%,因为 85 的 5 次方约 44.4 亿,刚好超过 2 的 32 次方(约 42.9 亿)。RFC 1924 用它压缩 IPv6 地址,PDF 内部的一些数据流也用它。缺点是字符表往低区延伸,空格一掺进来转义就麻烦,通用性上不去。
Base58 走的是另一个方向:它不是为了省字节,而是在 Base58 表上去掉 0、O、I、l,让人抄写比特币地址时不容易出错。
六、中文要算字节,不要算字数
Base64 只处理字节,不认识"字"。中文先经 UTF-8 变成字节,再进 Base64:"你好"两个汉字是 6 个字节(E4 BD A0 E5 A5 BD),编码后是 8 个字符 5L2g5aW9。长度公式里代的是字节数,这一步想错,后面全错。拿一段中文配置在 JSON 转 Base64 里转一次,能直接看到两个汉字变成六个字符的过程。
剩下所有数字都能自己复核:用 文件转 Base64 转一个 10 KB 的文件,看输出字符数是不是接近原始大小的 4/3。
速查小结
- 能安全传输的 ASCII 只有 94 个,2 的幂里最大能塞进去的是 64。
- 6 与 8 的最小公倍数是 24,于是 3 字节换 4 字符,输出长度必是 4 的倍数。
- 膨胀率上限 4/3,长数据约 33%,1 字节的短数据会涨到 300%。
- 想更省就付别的代价:Base85 省 8 个百分点但转义麻烦,Base32 费字但人能读。
- 算长度永远用字节数,中文先按 UTF-8 展开。