你是不是只会调用现成的 Base64 接口,却从来不知道它的底层到底在干嘛? 明明只是简单的编码转换,为啥会有 = 号填充?为啥 3 个字节能变成 4 个字符?为啥二进制串要拆成 6 位一组? 别再把 Base64 当成 “黑箱” 了! 今天这篇教程,就手把手带你剥开 Base64 的外衣,从二进制位运算到分组拆分规则,再到边界 case 处理,全程干货无废话,也是对上一篇文章《字符编码轻松学:从 ASCII 到 Unicode,再到 UTF 家族 —— 搞懂它们的 “前世今生”》实战的应用。 不用复杂的依赖,不用高深的知识,跟着步骤一步步敲代码,你也能亲手实现一个完整的 Base64 编码器,彻底吃透它的核心逻辑!
在进入今天的内容之前,咱们先快速回顾一下上一篇文章里讲过的 UTF-8表示码点范围。
常用汉字 UTF-8 占 3 字节,ASCII 字符占 1 字节;双字节、4 字节规则
因非本文所需,不再展开
接下来,我们再给出 Base64 编码中 0-63 索引对应的完整字符映射表。
有了上面的准备之后我们再来梳理下字符转base64流程,如下所示。
第一步、获取字符的 Unicode 码点
第二步、将码点转换为二进制字节
第三步、用UTF-8对二进制字节进行编码
第四步、按 6 位分组拆分UTF-8编码后的二进制串
第五步、分组映射 Base64 索引字符
第六步、若输出的字符不足4的倍数用等号(=)补充
对于不熟悉Unicode码点、ASCII码和UTF编码的可阅读上一篇文章
接下来,我们将逐一拆解不同类型字符(单个字母、数字、短单词、汉字)转 Base64 的完整过程 —— 涵盖字母 “a”、数字 “2”、单词 “HR”“NBA”,以及汉字 “你们”,从简单到复杂,把每种场景的编码逻辑讲透。
1、字母 a 转base64推导步骤如下
1.1、获取 a 的Unicode码点。方法有很多这里我用shell命令来获取
1.2、字母 “a” 的码点是十六进制 61(十进制 97)
//转二进制后仅占了7位不足8位
1100001
1.3、符合 UTF-8 单字节存储规则(0-127),对应的二进制为:
//左侧补0凑齐8位(只有左侧补0才不影响原二进制值大小)
01100001
1.4、按6位一组拆分二进制数据、不足6位右侧补0到6位(base64拆分规则)
// 拆分后如下所示
// 第一组 011000 对应十进制值为 24
// 第二组 010000 对应十进制值为 16
011000
010000
1.5、查询两组二进制值所对应的base64索引字符(上面👆🏻给出的索引表)
011000 -> 24 -> base64索引字符为:Y
010000 -> 16 -> base64索引字符为:Q
1.6、因 YQ 长度非 4 的倍数,补 = 至 4 字符(base64用等号区分该字符是否用到了补位且输出的长度必须为4的倍数)
//最终输出结果为
YQ==
我们再用shell验证,结果如下图所示
推导过程完全正确,再次用表格整理流程如下所示
2、数字 2 转base64推导步骤如下
2.1、获取 2 的Unicode码点
2.2、数字 “2” 的码点是十六进制 32(十进制 50)
//转二进制后不足8位
110010
2.3、符合 UTF-8 单字节存储规则(0-127),对应的二进制为:
//左侧补0凑齐8位(只有左侧补0才不影响原二进制值大小)
00110010
2.4、按6位一组拆分二进制数据、不足6位右侧补0到6位(base64拆分规则)
//拆分后如下所示
//第一组 001100 对应十进制值为 12
//第二组 100000 对应十进制值为 32
001100
100000
2.5、查询两组二进制值所对应的base64索引👆🏻
001100 -> 12 -> base64索引字符为:M
100000 -> 32 -> base64索引字符为:g
2.6、因 Mg 长度非 4 的倍数,补 = 至 4 字符(base64用等号区分该字符是否用到了补位且输出的长度必须为4的倍数)
//最终输出结果为
Mg==
3、单词 HR 转base64推导步骤如下
3.1、获取 HR 的Unicode码点。
3.2、HR 的码点分别是十六进制 48和52
//转二进制后不足8位
1001000 //字母 H 十六进制码点48转二进制
1010010 //字母 R 十六进制码点52转二进制
3.3、符合UTF-8 单字节存储规则(0x00-0x7F)范围,对应的二进制为:
//左侧补0凑齐8位(只有左侧补0才不影响原二进制值大小)
01001000 //字母 H 十六进制码点48转二进制
01010010 //字母 R 十六进制码点52转二进制
3.4、按6位一组拆分二进制数据、不足6位右侧补0到6位(base64拆分规则)
//3.4.1 将HR二进制按顺排列
01001000 01010010
//3.4.2 按照6位一拆分不足6位右侧补0
010010 //对应十进制 18
000101 //对应十进制 5
001000 //对应十进制 8
3.5、查询三组二进制值所对应的base64索引👆🏻
010010 -> 18 -> base64索引字符为:S
000101 -> 5 -> base64索引字符为:F
001000 -> 8 -> base64索引字符为:I
3.6、因 SFI 长度非 4 的倍数,补 = 至 4 字符(base64用等号区分该字符是否用到了补位且输出的长度必须为4的倍数)
//最终输出结果为
SFI=
4、单词 NBA 转base64推导步骤如下
4.1、获取 NBA 的Unicode码点。
4.2、NBA 的码点分别是十六进制 4E、42和41
//转二进制后不足8位
1001110 //字母 N
1000010 //字母 B
1000001 //字母 A
4.3、符合UTF-8 单字节存储规则(0x00-0x7F)范围,对应的二进制为:
//左侧补0凑齐8位(只有左侧补0才不影响原二进制值大小)
01001110 //字母 N
01000010 //字母 B
01000001 //字母 A
4.4、按6位一组拆分二进制数据、不足6位右侧补0到6位(base64拆分规则)
//4.4.1 将NBA二进制按顺排列
01001110 01000010 01000001
//4.4.2 按照6位一拆分不足6位右侧补0
010011 //对应十进制 19
100100 //对应十进制 36
001001 //对应十进制 9
000001 //对应十进制 1
4.5、查询四组二进制值所对应的base64索引👆🏻
010011 -> 19 -> base64索引字符为:T
100100 -> 36 -> base64索引字符为:k
001001 -> 9 -> base64索引字符为:J
000001 -> 1 -> base64索引字符为:B
5、汉字 “你们” 转base64推导步骤如下
5.1、获取 “你们” 的Unicode码点。
5.2、“你们” 的码点分别是十六进制 4F60 和 4EEC
//转二进制后不足16位2个字节
100111101100000 // 汉字 “你” 码点 4F60 二进制
100111011101100 // 汉字 “们” 码点 4EEC 二进制
5.3、符合_UTF-8 三字节存储规则(0x0800-0xFFFF)范围_
// 5.3.1 将字符的二进制长度补齐到2个字节16位
//左侧补0凑齐16位(只有左侧补0才不影响原二进制值大小)
//根据utf8三字节存储规则字符的二进制长度必须凑够16位所以左侧补1个0
0100111101100000 // 汉字 “你”
0100111011101100 // 汉字 “们”
// 5.3.2 根据utf8三字节存储规则进行拆分 汉字 “你” 和 “们” 的二进制
// utf8三字节拆分规则 1110xxxx 10xxxxxx 10xxxxxx (这里涉及utf8三字节储存规则不熟悉的请看上一篇文章’字符编码轻松学:从 ASCII 到 Unicode,再到 UTF 家族 —— 搞懂它们的 “前世今生”‘)
111001001011110110100000 //用utf8三字节编码后 “你” 的二进制长度达到 24位 3 字节
111001001011101110101100 //用utf8三字节编码后 “们” 的二进制长度达到 24位 3 字节
5.4、按6位一组拆分二进制数据、不足6位右侧补0到6位(base64拆分规则)
//5.4.1 将汉字“你们”经过utf8三字节编码后的二进制按顺排列
111001001011110110100000 111001001011101110101100
//5.4.2 按照6位一拆分不足6位右侧补0
111001 //对应十进制 57
001011 //对应十进制 11
110110 //对应十进制 54
100000 //对应十进制 32
111001 //对应十进制 57
001011 //对应十进制 11
101110 //对应十进制 46
101100 //对应十进制 44
5.5、查询八组二进制值所对应的base64索引
111001 -> 57 -> base64索引字符为:5
001011 -> 11 -> base64索引字符为:L
110110 -> 54 -> base64索引字符为:2
100000 -> 32 -> base64索引字符为:g
111001 -> 57 -> base64索引字符为:5
001011 -> 11 -> base64索引字符为:L
101110 -> 46 -> base64索引字符为:u
101100 -> 44 -> base64索引字符为:s
看到这里,大家应该能 get 到 Base64 的核心逻辑了 —— 它针对的是字符编码对应的二进制数据做 6 位分组处理。本文全程基于 UTF-8 编码展开,要注意的是,同一个汉字如果用 GBK 编码再转 Base64,得到的结果和 UTF-8 版本是不一样的。因此大家要留意因编码不同导致base64后不能相互解析问题。
上面的流程在精简总结下就是:
获取字符码点 -> UTF-8编码或其它编码 -> 6位一拆分 -> base64索引字符映射。
既然大家都看到这里了咱们在上一点难度推导最后一个案例。大家应该看到了我用的终端里面笑脸(😁)表情,怎样将它进行base64呢?
这个和上面的汉字不一样用到了_UTF-8四字节编码存储_。我先抛砖引玉快速推导下大家可以作为参考
printf 'U+%04X\n' \"😁\"
输出:U+1F601
U+1F601(十进制为 128513)超出了 UTF-8 三字节的存储范围(0x800-0xFFFF,十进制 2048~65535),因此需采用 UTF-8 四字节编码存储。
//😁码点U+1F601转二进制
11111011000000001
//utf8四字节存储规则
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
由utf8四字节存储规则可知有21个x空位需要补入
因此需要将 11111011000000001 补充到21位长度左侧补4个0
结果:000011111011000000001
笑脸😁的Unicode码点用utf8四字节表示就是
111100 001001 111110 011000 100000 010000
6位一拆分后索引与字符
111100 -> 60 -> 8
001001 -> 9 -> J
111110 -> 62 -> +
011000 -> 24 -> Y
100000 -> 32 -> g
010000 -> 16 -> Q
由于字符长度不足4的倍数因此补上2个等号结果为:8J+YgQ==
至此对base64剖析全篇结束。通过这篇文章的引导你是不是对base64有深入的认识了呢?不难看出,若要彻底吃透 Base64,充分掌握 UTF-8 编码 的相关知识是关键前提。
最后问大家一个问题base64为什么要6位一拆分呢?欢迎评论留言讨论。