什么是 Base62?
Base62 是一种只用在任何地方粘贴都能存活的字符来书写数字的方式:十个数字、26 个大写字母和 26 个小写字母。没有标点、没有空格、没有填充字符,没有任何需要先转义才能放进 URL、文件名、Cookie 或数据库键的东西。10 加 26 加 26 等于 62,名字由此而来。
30 秒版本:数字 12345 写作 3D7,1234567890 写作 1LY7VK。反方向是同一个运算的逆过程,但必须使用产生该短码的确切字母表 — 换一个字母表,同样的六个字符会变成另一个数字,而且没有任何报错。
本页做两件相关的事,它们的区别比本页其他任何事情都重要:
- 整数模式把输入当作数字。像
1234567890 这样的十进制 ID 变成 1LY7VK,输入的前导零不属于值本身,因此不会回来。
- 字节模式把输入当作字节串。字节被当作一个大端大整数读入,再按 62 整除,是对另一个量做的同样算术。这里前导零字节的数量会保留下来,每个零字节对应一个零字符。
62 从哪里来,代价是什么
62 不是 2 的幂,仅此一条事实就决定了其他一切。人们熟悉的编码是把输入切成固定比特组:4 比特印成一个十六进制字符,6 比特印成一个 Base64 字符,5 比特印成一个 Base32 字符。Base62 做不到这一点,因为没有整数的比特数能匹配一个 62 值的字符 — 5 比特只能表示 32 个值,6 比特是 64。所以 Base62 走的是算术路线:把整个输入读成一个整数,再以 62 为底印出。
于是一个字符承载 log2(62) = 5.954 比特,非常接近 Base64 的 6;在十进制模式下,一个字符顶替 log62(10) = 1.792 个十进制位。在决定长度之前,有几个值得知道的后果:
| 编码长度 | 可表示的值 | 实际意义 |
| 4 个字符 | 624 = 14,776,336 | 足够一个不超过八位数的计数器使用,且塞得进一个电话号码的宽度。 |
| 6 个字符 | 626 = 56,800,235,584 | ID 短码的常见形态。十位十进制 ID 六个字符即可容纳。 |
| 7 个字符 | 627 = 3,521,614,606,208 | 比 32 位计数器的范围高出两个数量级,正好是一个短链接别名的宽度。 |
| 11 个字符 | 6211 = 52,036,560,683,837,093,888 | 超过 264 = 18,446,744,073,709,551,616,完整的 64 位数十一字符即可容纳。 |
反过来看,1,000 个十进制位需要约 558 个字符,4,096 个十进制位需要 2,285 个。节省是实在的,但有上限:Base62 把十进制数缩短约 44%,而且永远比不过最密的打印编码,因为每字符 5.954 比特就是比 6.4 或 6.5 少。
整数模式:前导零的下场
整数只有一种规范的书写形式,它以字母表的零字符开头 — 在两种数字优先的排序中是 0,在字母优先的排序中是 A。第一个有效字符左侧的一切都是填充,而填充不属于值:00012345 和 12345 都变成 3D7。如果需要固定宽度 — 比如一个要在列中对齐的七字符短码 — 本页的把结果填充到固定宽度输入框会重复零字符直到达到宽度,所以宽度为 7 时 12345 在数字优先字母表中印作 00003D7,在字母优先字母表中印作 AAAADNH。填充只是外观:那些前导字符对值毫无贡献,解码任何一个字符串都还原出 12345,因为无论用哪个字符书写零,0 × 626 和 0 × 625 都是零。
因此解码带填充的字符串同样可行,页面会报告前导字符中有多少个是零字符,让「短码有七个字符」和「值只需要三个字符」之间的差别不再成谜。
字节模式:对字节串做同样的算术
字节模式把输入字节当作一个大端整数读入 — 无论多长,整个字节串被视为一个数 — 再把这个数以 62 为底印出。一个字节有 8 比特,而一个字符只承载其中 5.954 比特,所以字节模式不是压缩:它需要输入尺寸的约 134%,比 Base64 的 133% 略差。以上方尺寸表使用的 92 字节样本为例,92 字节印作 124 个 Base62 字符,Base64 同样印作 124 个(其中 123 个在它的 = 填充之前)。Base62 在密度上输给 Base64,在字符安全性上赢回来,这正是每个短链接和邀请码有意做出的取舍。
前导零字节会被保留,而这一点在两个方向上都容易出错。两个前导零字节后跟 0x01,在数字优先字母表中印作 001,在字母优先字母表中印作 AAB,因为前导字符书写零,且每个零字节对应一个。单个 0x00 的字节串印作单个字符 0,单个 0x01 印作 1。这是字节模式和整数模式运行同一算法却看起来不同的唯一一处。
这一切的代价是字节模式继承了算术的平方级耗时:1,024 字节估计约三分之一秒,2,048 字节约 1.3 秒,与上方限制表给出的数字相同。这就是本页字节模式出方向封顶 2,048 字节、回方向封顶 2,048 字符的原因 — 2,048 字节印出来约 2,751 个字符,所以约 1,520 字节是能完整通过本页往返的最大载荷 — 也是本页字节模式只作小值参考、不能替代 Base64 编码/解码 或 Base91 编解码 处理真实文件的原因。
三种字母表,以及为什么字母表就是全部
字母表的字符是一个固定序列,一个字符的值就是它在这个序列中的位置。挪动序列就改变了每个字符的含义。三种排序被广泛使用,下面是同一个数在每一种中的三种写法:
| 十进制 | 0-9 然后 A-Z 然后 a-z | 0-9 然后 a-z 然后 A-Z | A-Z 然后 a-z 然后 0-9 |
| 0 | 0 | 0 | A |
| 61 | z | Z | 9 |
| 62 | 10 | 10 | BA |
| 12,345 | 3D7 | 3d7 | DNH |
| 1,234,567,890 | 1LY7VK | 1ly7vk | BViHfU |
把最后三列当成一个字母表来读,它是一个自洽的方案。把它们当成可互换的来读,它就散架了,因为解码对字母表是全盲的,而且盲得最彻底:输入的任何字符都在全部三个字母表里,所以错误的字母表不会产生任何报错,只会产生另一个数字。
| 一个短码 | 按 0-9 A-Z a-z 读 | 按 0-9 a-z A-Z 读 | 按 A-Z a-z 0-9 读 |
| 3D7 | 12,345 | 13,957 | 211,665 |
| 1LY7VK | 1,234,567,890 | 1,624,950,792 | 48,723,527,772 |
大小写是同一个陷阱更尖锐的版本。Base62 在每种排序中都大小写敏感,所以把短码小写化会悄悄改写它:1LY7VK 在数字优先字母表中是 1,234,567,890,而 1ly7vk 在同一字母表中是 1,624,950,792 — 它恰好又是 1,234,567,890 在数字后小写字母的字母表中的值,因为那个排序正是数字优先排序交换两个字母大小写后的结果。零字符也不共享:两种数字优先排序把零写作 0,而字母优先排序把它写作 A,所以那个字母表根本不以数字开头。
Base62 与其他编码的对比
| 编码 | 每字符比特数 | 相对输入的体积 | 备注 |
| Base16(十六进制) | 4 | 200% | 每字节两个字符,一眼可读,无需查字母表。 |
| Base32 | 5 | 160% | 大小写不敏感、适合人口述,是这组里最浪费的。 |
| Base36(0-9 a-z) | 约 5.17 | 约 155% | 只有数字和小写字母,因此能挺过小写化流水线。12345 写作 9ix。 |
| Base58 | 约 5.86 | 约 137% | 为地址而非密度而生:去掉四个易混淆字符而不是给它们排序。见 Base32 / Base58 编码转换器。 |
| Base62 | 约 5.95 | 约 134% | 每个字符都是数字或字母,永远无需转义。密度介于 Base58 与 Base64 之间。 |
| Base64 | 6 | 133% | 各处的默认选择,代价是 +、/ 和 = 这三个强制转义的字符。见 Base64 转换器。 |
| Ascii85 / Base85 | 6.4 | 125% | 4 字节 5 字符,但按 85 整除,其字母表需要加引号。见 Ascii85 转换器。 |
| Base91 | 约 6.5 | 约 123% | 这组里密度最高的,也是输出未经转义就粘贴最不安全的。见 Base91 编解码。 |
百分比是四舍五入后的渐近开销;对一个真实 92 字节输入的实测字符数在本页上方的尺寸表里。整数模式下比较反过来看,因为输入是十进制文本而非字节:此时 Base62 比它替换的数字短约 44%,Base36 短约 36%,这就是 ID 短码通常用 Base62 或 Base36 而非 Base58 的原因。
Base62 不是什么
- 不是标准。没有 RFC,也没有规范。从未有标准组织定义过 Base62,这正是字母表没有保证的原因,也是本页把三种排序全部印出而不是悄悄挑一个的原因。
- 不是加密。只是换了一次基,仅此而已。任何知道字母表的人一遍就能还原,没有密钥、没有盐、没有任何秘密。
- 不是压缩。字节模式让数据增长约三分之一。整数模式看似压缩,只是因为十进制文本本来就是写数字的低效方式。
- 不是大小写不敏感。Base32 可以小写后再读回来;Base62 不行。一个经过大小写规范化的系统的短码就是损坏的短码。
- 不能自校验。没有校验和也没有校验字符,单个错误字符会给出一个看起来有效但值不同的结果。如果这很重要,Bitcoin 排序自带校验和 — 见 Base32 / Base58 编码转换器 的 Base58Check 一节。
- 不是定长。字符数随值增长,一个从 1 涨到 627 的计数器会从一个字符变成八个。需要稳定列宽时请填充到固定宽度。
真正咬人的陷阱
- 十有八九的谜团,答案都是字母表。如果另一个工具对同一个数给出不同的短码,先逐字符比较两边的字母表,再怀疑是否有 bug。上表显示了同一个值在三个字母表中的样子,三者全都正确。
- 错误的字母表静默失败。它没有报错路径:
3D7 可以解码成 12,345、13,957 或 211,665,只取决于假定了哪种排序。检查首字符(它最可能是填充零),并检查字母表开头是数字还是字母。
- 前导零有两种不同含义。整数模式中
00012345 和 12345 是同一个数,印出来一模一样。字节模式中前导零字节是数据,会印作一个额外的零字符。丢掉其中一个字符就改变了字节串。
- 大小写规范化会毁掉短码。电子邮件地址、主机名或 Windows 路径都可能在传输中被小写化。Base62 短码挺不过这种处理,这正是 Base36 仍然存在的原因。
- 长输入不只是大,还慢。转换是对整个输入一次性做的算术,输入长度翻倍,时间翻四倍。本页在超过实测限制时直接拒绝而不是挂起;上方尺寸表给出了实测数字。
- Base62 字符串本身证明不了任何事。既然几乎任何数字字母串都能无抱怨地解码,任意 URL 别名都会解出某个数字。短码只有当发放它的系统同时保存了映射时才有意义。
快速问答
为什么另一个 Base62 工具对同一个数给出不同的短码?
因为字母表不同。Base62 没有可供分歧的规范,实现者各选一种排序:数字和大写在前、数字和小写在前,或字母在数字之前。切换本页顶部的字母表下拉框,就能并排看到三种答案。
Base62 在 URL 里安全吗?
安全,在全部三种排序中都安全。全部 62 个字符都是无需转义的字母和数字,查询串里没有任何一个会被展开成百分号三元组,也没有任何一个会终结 URL、路径段或 HTML 属性。这正是短链接用 Base62 而非 Base64 的全部原因。
Base62 和 Base64 哪个更小?
对字节而言,Base64 略小一点:约为输入的 133%,Base62 约 134%。对上面的 92 字节样本,两者持平,都是 124 个字符。对十进制数字而言,Base62 把文本缩短约 44%,这才是实际的收益所在。
能用本页解码 Base64 字符串吗?
不能,而且它不会告诉你失败了。两个字母表在全部 62 个字符上重叠,所以 Base64 字符串通常在本页也是「合法」输入,只会解出另一个数字。解码 Base64 请用 Base64。
Base62 短码有最大长度吗?
理论上没有,算法没有上限。实际上本页止步于 4,096 个十进制位或 2,048 字节,因为它背后的算术是平方级的,标签页会卡死而屏幕上没有任何解释。本页上方的限制表列出了实测数字。
怎么知道本页的结果是对的?
按自测。它会运行每个已记录的示例、0 到 64 的每个字节长度和一组十进制数字,遍历全部三种字母表,把返回值与输入比较,并复查每个字母表的 62 个字符在 URL 中是否全部安全。示例按钮还会把它的三个结果与本页数据文件中记录的值对比,那些值已由本站测试工具中第二套独立的大整数实现验证过。
标准与致谢
这里没有可供实现的标准:Base62 是一种约定,不是规范,上面印出的三种字母表就是真实软件中出现的那三种。本页用本站自己的大整数例程实现它,直接在数字数组上做普通算术,因此任何尺寸都不会被 JavaScript 的 number 类型限制。
本页文字为本站撰写。页面上每个算过的例子都由页面自己的代码产生,并经本站测试工具中第二套基于原生大整数的独立实现验证;同一实现也验证了数据文件中的示例表和尺寸表。