Base64 的原理和思考

897 阅读7分钟

我正在参加「掘金·启航计划」


Base64 是很常见的一种数据编码格式,它使用 ASCII 字符中的 64 个可见字符来编码二进制数据。在开发过程中或多或少都能用到它。

Base64 的应用场景

  • URL

    在 URL 中一些字符有特殊含义,比如/?=#。当我们需要在 URL 中传递一些 URL 不支持的字符或者受限制的字符时,就可以先经过 Base64 编码之后再进行传递(URL 用的 Base64 编码经过优化,替换了+/-_

  • 在文本文件中存放二进制数据

    在 xml、JSON 等文本格式的代码中,想要使用一些如图片秘钥等二进制格式的内容时,Base64 也是一个很好的选择

除了上面的两种场景外还有很多其他的情况,总结来书就是在一些只能使用纯文本或者只能使用受限的部分文本的情况下希望传输一些完整的原始数据,Base64 就是很好的选择。因为 Base64 使用了最通用的 64 个 ASCII 字符,能在很多场景下发挥作用。

Base64 的编码原理

Base64 使用 大写字母 A 到 Z 、小写字母 a 到 z 、数字 0 到 9 还有 + 和 / 来编码数据(在一些特殊版本的Base64 中,+ 和 / 会被替换为其他字符)。

分别用它们表示从 0000000000111111 的刚好 64 个二进制数据。

Base64 码表:

索引二进制对应字符索引二进制对应字符索引二进制对应字符索引二进制对应字符
0000000A17010001R34100010i51110011z
1000001B18010010S35100011j521101000
2000010C19010011T36100100k531101011
3000011D20010100U37100101l541101102
4000100E21010101V38100110m551101113
5000101F22010110W39100111n561110004
6000110G23010111X40101000o571110015
7000111H24011000Y41101001p581110106
8001000I25011001Z42101010q591110117
9001001J26011010a43101011r601111008
10001010K27011011b44101100s611111019
11001011L28011100c45101101t62111110+
12001100M29011101d46101110u63111111/
13001101N30011110e47101111v
14001110O31011111f48110000w
15001111P32100000g49110001x
16010000Q33100001h50110010y

下面举例说明Base64的编码方式。

假设我们要编码的内容为China,先将其转换为二进制字节流,一般用一种文字编码格式将字符串编码为二进制字节流,常见的如 ascii、utf-8、utf-16等,为了方便演示,而且要编码的字符串只有英文,我们选择 ascii(使用utf-8 可以兼容unicode 中的所有字符)。

对照 ascii 码表,China 对应的二进制数据为:01000011 01101000 01101001 01101110 01100001

编码的方式如下:

1、将原始数据按每3个字节为一块进行编码。一共3*8=24个bit位。最后不足3个的单独一块。

image.png

2、将24个字节分为4组,每组6个bit。不足的在后面补0

image.png

4、在每组6个bit前插入两个 0 bit,变为8 * 4 = 32 个bit。

image.png 5、对照Base64码表,不足4个字节的部分填充“=”,得到最终编码后的数据:Q2hpbmE=

image.png

从上面的分析可以看出,Base64 将3个字节的数据最终编码成了4个字节。体积是原来的 4/3 倍。

思考:为什么是 base64 而不是 base32 或者 base128 ?

Bae64的理念就是用可见字符编码任意的二进制数据。从上面的分析可以看出,编码后每个字节都有两位的0,造成了空间浪费,这也是Base64编码体积增大的原因。

那我们能不能将这两位利用起来呢?

如果我们增加插入 0 的数量,我们可以使用更少的字符来编码数据。比如,将插入的 0 减少一位,这样我们就只需要32个字符就可以编码任意二进制数据,这样就不需要使用到任何特殊字符,字母和数字就能完全满足需求,但这会造成每个字节编码数据的位数减少,这样会更加增加最终结果的体积。

如果我们减少插入 0 的数量,我们就需要更多的字符来编码数据。假设我们将插入的 0 减少 1 位,这样我们就有了7位的编码空间可用,同时编码的方式会变成 使用 8个字节来编码7个字节的原始数据。

空间的利用率好像提升了,但是真的如此吗?

为了表示这 7 位的二进制数据,我们需要 128 个可见的字符来编码数据。然而,ascii 码中只有 95 个(包含空格)可见字符,其余的都是一些控制字符,完全不够。这时候有人可能会问,为什么非要使用 ascii 字符?unicode 不是有很多很多可见的字符码,甚至我们可以用上汉字,这远远超过128个呀。

是的。单从字符数量来考虑确实如此,但是,我们应该考虑到编码后字符串的体积,这里以最常见的utf-8编码格式举例。

在utf-8格式中,前面部分与ascii字符完全兼容,都是1个字节,但是其余的字符,都是以2个甚至更多的字节来编码的。虽然从视觉上看,编码后的结果字符整体数量变少了,但是由于使用了大量的非ascii字符,最终这一串字符串在传输和存储过程中所占用的体积反而变大了,比使用base64编码还要更大。

总结

虽然减少插入0的数量可以减少最终输出的字符的数量,但是由于字符集编码的限制,导致最终产生的数据的体积反而变大了。

反之增加插入0的数量可以减少使用到的字符的数量,但同时也会导致最终输出的字符变得更多,也会增加数据的体积。

所以综合考虑,base64确实已经是目前最优的使用可见字符来编码数据的方式。

但在具体的某些应用场景下,我们就可以利用这种算法思路来做一些有用的事。

比如如果不考虑体积的增加,不管是 base64 还是 “base128” 都可以通过替换字符集让我们使用更多的字符,我们可以生成一些只有汉字或者emoji表情的字符串来编码二进制数据。可以做一些有趣的事情。