base64编码技术原理分析

15 阅读5分钟

背景

本文我们尝试回答如下这个问题: 我们有一个字符串:大家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个。

ASCII table reference

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 ~ 1270xxxxxxx1
128 ~ 2047110xxxxx 10xxxxxx2
2048 ~ 655351110xxxx 10xxxxxx 10xxxxxx3
65536 ~ 111411111110xxx 10xxxxxx 10xxxxxx 10xxxxxx4
"大".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编码:

  1. 三个字节为一组,如果字节数不能被3整除,用0字节末尾补齐
  2. 按照6个比特位一组进行分组,前两位补0,凑齐8位
  3. 计算每个分组的数值
  4. 以第3步的值作为索引,去base64对照表找对应值
  5. 如果尾部是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/

附录:base64编码表