这是一种比base-64
更加节省空间的编码方式。github地址
1.概述
在网络编程中,我们常需要利用一些文本传输协议(如POP3
和SMTP
)协议来传输二进制数据。
这时候必须要先将它们转化为文本格式才能进行传输,最常用的编码方式就是base-64
。它虽然简单方便,容易实现,但是将会带来33%
的数据量增长。
2.一个小例子
有些时候我们想把图片、字体、音频等二进制文件作为url
的一部分传输给客户端/服务器,这样可以减少一次请求和响应。当然,我们必须保证这些文件不能太大。
比如,原来是这样写的:
<img src="example.png" />
为了避免这次额外的资源请求,我们可以利用base-64
编码从而换成下面的写法:
<img src="https://img-blog.csdnimg.cn/2022010614283130871.png" />
虽然这种做法可能对于小文件来说确实可以优化整个数据传输的效率,但是如果不慎用于大文件将很可能导致性能的下降。原因就是base-64
编码会导致额外增加33%
的传输量。下面讲简要分析其原因。
3.base-64编码
base-64
是将二进制数据转换为文本数据的最常用方法,比如说要把一个字节的二进制数据–01101001
插入到一个文本文件中。最直接的方法就是找两个字符分别与0
、1
对应,如果我们选择的是A
和B
,那么转换的结果就是下面这样的(假设这是一个只有1个
像素的图片文件)。
<img src="data:image/png;sillyEncoding,ABBABAAB" />
虽然实现起来很简单,但是编码后的数据会变为原来的八倍。
对于base-64
,他会把数据按照每6个
位分为一组(一共有64
种组合方式),每种组合方式(编号为0
~63
)各对应一个字符,然后进行编码。这样转换前后的数据量之比就为8:6
。
如果原数据的数据量(以位为单位计算)不是6
的倍数,添加1个
或2个
额外的字节来凑足24位
,然后分成3组
后再对应编码。但是这1个
或2个
额外添加的字节不参与编码,而是最后统一转换成1个或2个=
号。
那么,如果使用base-64
来编码上面的图片文件,结果将变成:
<img src="https://img-blog.csdnimg.cn/2022010614283134183.png" />
base-64
编码使用的字符包括26个大写字母
+26个小写字母
+10个阿拉伯数字
,最后还有两个特殊符号+
和/
。
为了改良base-64
,我们可以考虑使用其他的编码字符,但是ASCII
的限制太多,很难再找到更加高效并且简单可行的映射关系,所以我们下面尝试在UTF-8
中寻找解决方案。
4.UTF-8编码
因为大部分的网页文件(尤其是有汉字的)其实是由UTF-8
编码而成的,所以我们的想法是完全可行的。
下面我们开始介绍关于UTF-8
的一些核心概念。
UTF-8
是一种对于Unicode
的可变长字符编码方式,一共支持1,112,064
个符号点。所谓符号点,其实就是可以表示为一个字符的数字(在Unicode
中,并不是所有的数字都可以转换表示成一个字符)。对于ASCII
字符的编码,可以只用一个字节;但是对于某些的符号点,就可能需要使用多达4个
字节来表示这个字符。
Unicode中符号点的范围 | UTF-8编码 | 所用的总位数 | 符号点所占的位数 | 增加率 |
---|---|---|---|---|
0x00 – 0x7F | 0xxxxxxx | 8 | 7 | 8:7 |
0x80 – 0x7FF | 110xxxxx 10xxxxxx | 16 | 11 | 16:11 |
0x800 – 0xFFFF | 1110xxxx 10xxxxxx 10xxxxxx | 24 | 16 | 24:16 |
0x10000 – 0x10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 32 | 21 | 32:21 |
上面的增加率,就是表示该范围内的符号数所用的位数和实际上真正有效的位数(其他的位只是作为辅助标识)。
如果使用上表中的单字节编码方式来将我们的二进制数据转换成UTF-8
编码,那么结果将会是下面这样子的:
如果使用这种每7位1组
的编码方式,那么编码之后得到的数据量将从base64
的8:6
下降为8:7
。
但是很遗憾,如果在网页中使用这种编码方式,将会引起冲突,不然base64
也不会从ASCII
编码字符只中选取其中的64个
作为目标字符,原因还是为了避免在网页解析时发生冲突。
比如,我们转换后的数据一般是以下面这种形式出现在网页代码中的:
<img src="data:image/png;ourEncoding,(Encoded data)" />
(如果数据中出现",这个字符就可能使浏览器误以为是字符串的结束,进而造成数据截断)。
5.如何避免冲突
为了保证编码后得到的字符不会在HTML
页面中引起冲突,我们需要找出所有的“不安全”的字符。(比如双引号"
)
除了双引号"
以外,还有换行字符\n
、回车字符\r
、转义字符\
和&
、不可显示的null字符
–0x00
都会引起解析冲突。
这样,把上面的6个
”不安全“字符剔除后,只剩下122个
字符。
6.base-122编码
这样我们的方案基本成型了,将原二进制数据按照每7位
为一组(如果数据长度不是7
的倍数,则需要填充),然后直接转换为UTF-8
中的单字节字符,0xxxxxxx
。
如果转换后的字符不凑巧就是6个
“不安全”的字符之一,则做进一步的转换,将其变成双字节的UTF-8
字符:110xxxxx 10xxxxxx
。因为只有6个
字符,其实只需要3个
位就行了,这里我们使用sss
来表示这些实际有效的数据位,110sssxx 10xxxxxx
。
那么剩下来还有8个
位可以存放数据,这8个位
完全足够存放下一组的7个位
的数据。这样我们选后面的7位
来存放下一组的数据即可,110sss1x 10xxxxxx
(sss
用来标识不安全的6个
字符,xxxxxxx
用来存放下一组的数据)。
base-122
编码的最终效果如下:
终于,我们完成了编码前后数据比为7:8
的目标,编码同样的数据,所占用的数据量是base-64
的86%
。
7.如何使用
使用前必须保证你的HTML
文件使用的是UTF-8
编码格式。
<head><meta charset="utf-8"></head>
用法见github
首页的介绍。