某次和小伙伴对问题,他指着一串字符串问我:你知道这里为什么有两个等号(=)吗?
我:我不知道......
他:因为这是base64编码,@##@%^&*&%#@@#$%^,位数不够所以用=补齐。
我:哦~(点头假装听明白了)
经小伙伴这么一说,我突然想起来以前面试的时候,好像也被面试官问过base64编码相关的问题,当时除了知道这是一种编码方式以及前端会用它进行图片编码之外,对于它具体的编码规则(WHAT、WHY、HOW)都不了解,所以今天就去仔细看了一遍,在这里整理一下吧。BTW,去学习某个知识的原因并不是因为面试会问到,而是因为......学到就是赚到嘛!^ ^
一、WHAT
什么是base64编码?它是基于64个可打印的字符来表示二进制的数据的一种方法,哪64个可打印的字符呢?
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/'] (26 + 26 + 10 + 2 = 64)
这64个字符可以称作base64的索引表,这是标准的base64协议规定。为什么叫base64呢?因为它是基于这64个可打印字符,用6位2进制数()表示字符,因此称为base64。同理可得,base32就是用5位2进制数、base16就是用4位2进制数去表示字符。
二、WHY
为什么要使用这种编码方式呢?它本质上不还是二进制吗?为什么不直接传输二进制,而再用base64的方式去转换呢?
可能有人会觉得base64是用来加密的——但其实并不是,因为如果我们稍微了解一下它的编码规则就会知道,它是很容易能被反向解码出来的。(当然可以通过打乱编码顺序来达到加密的目的,这种情况这里先不讨论)那为什么要用base64编码呢?真正的原因是二进制不兼容。某些二进制值,在一些硬件或软件上,比如在不同的路由器、老电脑、老软件、网络协议上,表示的意义不一样,做的处理也不一样。这就导致那些不兼容的二进制值被错误处理,这肯定是不行的嘛。所以就先把传输数据做一个base64编码,统统变成可见字符(各个软件硬件都兼容的二进制值),这样出错的可能性就大大降低了。
三、HOW
接下来说说base64是怎么编码的呢?
- STEP 1:将需要转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。(这里为什么要每三个字节一组呢?因为每个字节占8bit,而base64是用6位二进制表示字符,8和6的最小公倍数是24,所以三个字节一组刚刚好~)
- STEP 2:将上面的24个二进制位每6个一组,共分为4组。
- STEP 3:在每组前面添加两个0(注意这里,编码之后长度会变长),每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
- STEP 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
Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,因此,base64编码之后的文本,要比原文增加三分之一。
举个具体的🌰:对“Hah”进行base64编码:
- “H”、“a”、"h"对应的ASCII码值分别为72,97,104,对应的二进制值是01001000、01100001、01101000。如下图第二三行所示,组成一个24位的二进制字符串。
- 将24位每6位二进制位一组分成四组。
- 在上面每一组前面补两个0,扩展成32个二进制位,此时变为四个字节:00010010 、00000110、00000101、00101000。分别对应的值(base64编码索引)为:18 、6、5、40。
- 用上面的值在base64编码表中进行查找,分别对应:S 、G、F、o。
文本 | H | a | h |
---|---|---|---|
ASCII码 | 72 | 97 | 104 |
二进制值 | 01001000 | 01100001 | 01101000 |
6bit一组 | 010010 、000110、000101、101000 | ||
base64索引 | 18 、6、5、40 | ||
base64编码 | S 、G、F、o |
因此“Hah”base64编码之后就变为:SGFo。
上面我们提到编码的第一步是“每三个字节”一组分组,那要转换的字符没有三个字节怎么办?比如现在要对“A”和“BC”进行base64编码~
一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个base64编码,而后面两组没有对应数据,都用“=”补上。因此,“A”编码之后为“QQ==”;
两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个base64编码,第四组完全没有数据则用“=”补上。因此,“BC”编码之后为“QKM=”。
(终于明白开头那位小伙伴的意思了~)
四、前端场景——图片编码
我们知道,我们所看到的网页上的每一个图片,都需要消耗一个http请求下载,要是图片的下载不用向服务器发出请求,而可以随着HTML的下载同时下载到本地就好了,base64正好能解决这个问题。
比如一个基于base64编码后的图片地址长这样:
// 我只是一个🌰 data:image/gif;base64,R03t7txgBjboSvB8EpLoFZywOAo3LFE5lYs/QW9LT1TRk1V7S2xYJADs=
我们将其放在HTML或者CSS中,可以不向服务器发出请求就能拿到图片。
需要注意的是,前面我们提到,使用base64有一个问题是编码之后体积会增大1/3,所以使用base64不一定就能提升性能,需要考虑具体的场景。比如,如果图片体积很小且因为用处的特殊性无法被制作成雪碧图,在整个项目的复用性很高且基本不会被改变,那么此时或许可以使用base64编码传输图片(比如页面的背景图)。