背景
本文我们尝试回答如下这个问题:
我们有一个字符串:大家Dj
,对其进行base64
编码之后得到5aSn5a62RGo=
。这其中的转换机制是什么?
ASCII编码
ASCII(American Standard Code for Information Interchange, 美国信息交换标准代码),共有128个字符,一个字节,就可以表示所有ASCII字符。包括33个控制字符,a-z、A-Z、0~9等其他可见字符。
ASCII码可以方便展示英文字符,但对于其他语言和语系,128个字符是不够用的。比如常用的汉字可能有5千多个,就需要用多个字节来表示一个汉字符号,简体中文常见的编码方式有GB2312就使用两个字节来表示一个汉字,GB2312理论上最多可以表示65536个。
Unicode码
Unicode为了解决ASCII码的局限而产生的。unicode的目标是为世界上的所有的字符都提供一个唯一的编码,比如汉字“谷”的Unicode码为U+8C37
,英语大写字母“A”为U+0041
,希腊字母Σ
的Unicode码为U+931
。
Unicode码定义了每个字符二进制编码,但没有定义这个二进制编码在如何存储的。比如汉字“严”的unicode是4E25
,转换二进制数是100111000100101
,共15位,至少需要2个字节才能存储;因为字母“A”的unicode是0041
,二进制数是1000001
,共7位,只需要1个字节;不同符号的unicode编码存储所需的二进制长度不同。
这里有两个问题需要考虑。第一个问题是,如何区分Unicode和ASCII?计算机如何知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,不同的字符需要的字节数不同,如果我们用统一的短字节来表示存储unicode,那么会出现不够用的情况;但如果统一长字节来存储,可能会出现存储浪费的情况,如何有效的存储unicode是一个技术问题。
UTF-8
UTF-8(8-bit Unicode Transformation Format)是一种正对Unicode的可变长度的字符编码。它可以用一至四个字节对Unicode字符集中的所有有效编码点带你进行编码。
“大家”的base64编码是多少?-->
范围(十进制) | UTF-8编码(二进制) | 字节数 |
---|---|---|
0 ~ 127 | 0xxxxxxx | 1 |
128 ~ 2047 | 110xxxxx 10xxxxxx | 2 |
2048 ~ 65535 | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
65536 ~ 1114111 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
"大".charCodeAt(0) // 22823
因为2048 < 22823 < 65535
,所以需要三个字节对它进行编码。将22823
转成二进制:
(22823).toString(2) // 101100100100111
获得二进制101100100100111
进行如下UTF-8转码流程:
101 100100 100111 // 从右往左六个一组分割
11100101 10100100 10100111 // 1110xxxx 10xxxxxx 10xxxxxx
同理“家”的UTF-8编码为
"家".charCodeAt(0) // 23478
(23478).toString(2) // 101101110110110
101 101110 110110 // 从右往左六个一组分割
11100101 10101110 10110110 // 1110xxxx 10xxxxxx 10xxxxxx
"D"的UTF-8编码为
"D".charCodeAt(0) // 68
(68).toString(2) // 1000100
01000100 // 0xxxxxxx
"j"的UTF-8编码为
"j".charCodeAt(0) // 106
(106).toString(2) // 1101010
01101010 // 0xxxxxxx
因此“大家Dj”的UTF-8编码为:
11100101 10100100 10100111 // 大
11100101 10101110 10110110 // 家
01000100 // D
01101010 // j
然后我们按照二进制
转base64
的规则进行base64编码:
- 三个字节为一组,如果字节数不能被3整除,用0字节末尾补齐
- 按照6个比特位一组进行分组,前两位补0,凑齐8位
- 计算每个分组的数值
- 以第3步的值作为索引,去
base64对照表
找对应值 - 如果尾部是
00000000
,则值为=
// 1、补齐字节数为3的倍数
11100101 10100100 10100111 // 大
11100101 10101110 10110110 // 家
01000100 // D
01101010 // j
00000000 // 补
// 2、6比特一组进行分组
111001
011010
010010
100111
111001
011010
111010
110110
010001
000110
101000
000000
// 3、前两位补0,凑齐8位
00 | 111001 => 57 = '5'
00 | 011010 => 26 = 'a'
00 | 010010 => 18 = 'S'
00 | 100111 => 39 = 'n'
00 | 111001 => 57 = '5'
00 | 011010 => 26 = 'a'
00 | 111010 => 58 = '6'
00 | 110110 => 54 = '2'
00 | 010001 => 17 = 'R'
00 | 000110 => 6 = 'G'
00 | 101000 => 40 = 'o'
00 | 000000 => 0 = '='
// 结果
5aSn5a62RGo=
上述结果可以在线验证:www.base64encode.org/