base64 原理及转换会变大的原因

以前用到base64有这么几个场景:

  1. 使用html2canvas 将html 转成图片的时候,html 里面的跨域图片无法生成图片,所以将图片先转成base64再生成
  2. jpg之类的图片有损压缩,使用canvas.toDataUrl()
  3. 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
通过上表可以得知分别为:

image

2. 将 ASCII 值转换为二进制

image

算数不好的同学(我)试试这个方法:
image

通过上面可以知道,其实 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%

重新分组后新的值为:

image

同样计算过程为:

image

4. 接下来就是根据新的值在base64表查找对应的字符了

索引对应字符索引对应字符索引对应字符索引对应字符
0A17R34i51z
1B18S35j520
2C19T36k531
3D20U37l542
4E21V38m553
5F22W39n564
6G23X40o575
7H24Y41p586
8I25Z42q597
9J26a43r608
10K27b44s619
11L28c45t62+
12M29d46u63/
13N30e47v
14O31f48w
15P32g49x
16Q33h50y

最后的结果为:

image

这时候转换的结果是SGVsbGP

因为规定不足4字节的要在后面补 =

所以结果为SGVsbGP=

本以为这样就ok了,但是我去在线base64 加解密 算了下结果为 SGVsbG8=

什么?最后一位是8? 看了下base64 表里对用的值为60,也就是 parseInt(111100,2),仔细想了下,每三个字符一组,如果只有两个,那最后一个字符就是00000000,分组的时候 就变成了111100。
所以纠正后的结果为:

image

表格地址获取: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个字符可以用