base64 原理及转换会变大的原因
以前用到base64有这么几个场景:
- 使用html2canvas 将html 转成图片的时候,html 里面的跨域图片无法生成图片,所以将图片先转成base64再生成
- jpg之类的图片有损压缩,使用canvas.toDataUrl()
- webpack 打包时,为了减少 https(http) 的请求数量(主要是为了减少三次握手,如果是http1.1开启了长连接或者http2.0/3.0之类的有多路复用,其实我觉得可以不用转base64),将小于10k(好像是10k,记不清了)的图片转成base64打在代码里。
但是,图片转成base64,体积会变大33%,所以大点的图片也不建议转换。
那么,转成 base64 为什么会变大呢?
还有为啥叫 base64, 不叫base16或者base24、base32呢?
针对上面的疑问,我们一步步来解释,别急,老弟!
base64 生成步骤
假设我们有个单词要转成base64: Hello
1. 找到每个字母对应的ASCII值
ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 |
---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | " | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
通过上表可以得知分别为:
2. 将 ASCII 值转换为二进制
算数不好的同学(我)试试这个方法:
通过上面可以知道,其实 ASCII 码只有128个,也就是 7位就够了,但是存储确是用的 8 位,每个都是在前面补0,其实已经浪费了一些空间。假如说以后字符补充到256个,就可以充分利用空间了。
3. 将 ASCII 的二级制值每6个一组,重新组装(如果不足六位,在前面补0),并计算对应的10进制的值
那疑问来了,为什么是6个一组?
首先常用字符有a-z、A-Z、0-9
这些字符总共 26 + 26 + 10 = 62个,另外有找了2个凑数的字符 + /
其实我觉得其他字符拿来凑数也是可以的,例如 -、#、[、]等。
2的6次方等于64,也就是6位就足以覆盖全部的base64字符了,所以是6位。
但是存储还是按8位一组来的,所以要在前面补2个零。
6 和 8 的最小公倍数是24,也就是3个字节,每 6 位一组,也就是4组,每组前面补2个0,凑够8位。所以这个时候就变成了4个字节,也就是增加了33.33%
重新分组后新的值为:
同样计算过程为:
4. 接下来就是根据新的值在base64表查找对应的字符了
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
最后的结果为:
这时候转换的结果是SGVsbGP
因为规定不足4字节的要在后面补 =
所以结果为SGVsbGP=
本以为这样就ok了,但是我去在线base64 加解密 算了下结果为 SGVsbG8=
什么?最后一位是8? 看了下base64 表里对用的值为60,也就是 parseInt(111100,2)
,仔细想了下,每三个字符一组,如果只有两个,那最后一个字符就是00000000,分组的时候 就变成了111100。
所以纠正后的结果为:
表格地址获取:https://docs.qq.com/doc/DU0tod0JXQ0J3UFRp
为什么是Base64,可以是Base16或者Base512吗?
前面已经说到ASCII码有128个,即 2 的 7 次方,一个字节等于8个无符号位,这种情况下,已经浪费了 1 个符号位,Base64 是 6位,假如说我们用4位的,也就是2的4次方16,可以命名为Base16,这个时候转换会有什么后果呢? 相当于一个字节拆成2个字节,4和8最小公倍数为32,也就是4个字节一组,但是重新组装后,长度变为原来的2倍,所以体积变为原来的2倍。这时候应该清楚为什么是Base64了吧,就是为了减少空间的浪费。那可以发明Base256、或者Base512 这种东西来节省体积吗?如果是Base256,也就是2的8次方,跟现在的位数一样,但是用不同的符号代替,则可以保证在不增加体积的情况下完成转换。如果是Base512,则是9位了,一个字节才8位,那就不行了。这么说来Base64 还有优化空间,上限就是Base512,哪天我高兴了,我也给编个Base512的表,发明Base512编码,哈哈哈!!!
然而实际情况是我并没有512个字符可以用