该篇源于部门的一个关于计算机编码的分享会,还挺有意思的,这里自己也整理一篇。
1. 编码和解码
1.1 编码是什么?
之前在学习Http的时候,稍微学习了一下编码和加密: Android 深入Http(2)加密与编码
总的来说,将一种数据格式转化成另一种易于被计算机识别数据格式称为编码,而其逆向的过程称为解码。
↑这是大部分文章对编码的概念解释,我们都知道计算机硬件只认0和1,所以“易于被计算机识别的数据格式”也就是二进制格式。
所以可以总结成下面这个公式:
编码 = 任意格式数据 -> 二进制数据
解码 = 二进制数据 -> 任意格式数据
一个人类使用的字符 -> 0和1组成的二进制数据
1.2 编解码解决什么?
既然编码的结果是产生二进制的数据,所以编解码显然是人类与计算机的交流方式。再任何的有关软件和硬件的相互操作中,都需要使用编码,对于Android、Java来说,就是I/O操作了,比如:
- 数据持久化
- 网络数据传输
2. 几种编码方式
2.1 ASCII及扩展集
在计算机早期时,并没有一套统一的编码算法,每个人都能自己定义编解码,而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码,统一规定了常用符号用哪些二进制数来表示。
2.1.1 ACII码
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码) 是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最早也是最通用的信息交换标准。
ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,共定义了128个字符。
下表为最终规范的ASCII码:
ASCII是一种无符号单字节 的编码方案,基于文本数据,所以它的取值范围是 0~127(10)
其中 第0-31、127个是不可见字符(即控制字符),第32-126是常用的可见字符
2.1.2 ISO字符集
由于标准 ASCII 字符集字符数目有限,无法满足要求。为此, ISO(International Organization for Standardization,国际标准化组织) 又制定了 一系列标准,它规定了在保持兼容的前提下将 ASCII字符集扩充为 8 位代码的统一方法(即双字节编码)。 也就是说可以使用的字符从原来的128个扩充到了256个,其中的前128个是ASCII码,即 ISO-8859
但是在当时,由于每个国家和地区,都有自己的语言和输入系统,所以256个字符难以统一各个区域,所以ISO为每个区域规定了他们自己的标准,例如:
- ISO-8859-1:又称“西欧语言”;可使用的语言例如:荷兰语、西班牙语、德语、意大利语等。
- ISO-8859-2:又称“中欧语言”;可使用的语言例如:克罗地亚语、捷克语、匈牙利语、波兰语等。
- ISO-8859-3:又称“南欧语言”;可使用的语言例如:世界语、马耳他语等。
- ISO-8859-4:又称“南欧语言”;可使用的语言例如:爱沙尼亚语、格陵兰语、拉脱维亚语等。
这也说明了 ISO-8859不是一个字符集方案,而是一个系列的字符集方案,同样的码位可以映射不同的字符集。
2.2 国标
作为中国人,我们在使用计算机的时候,自然的会使用到汉字,而上述提供的 ASCII和ISO中,都不支持汉字的字符,所以国内也搞了一套方案。
汉字有上万字,但是常用的只有几千个,最最常用的也就3000个左右。
2.2.1 GB 2312
GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)。
区字节含义:
GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。
- 01-09区为特殊符号
- 16-55区为一级汉字,按拼音排序
- 56-87区为二级汉字,按部首/笔画排序
- 10-15区及88-94区则未有编码。
举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。
汉字区的内码范围高字节从B0-F7(176-247),低字节从A1-FE(161-254),占用的码位是72*94=6768,其中有5个空位是D7FA-D7FE。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节) 0xA1(第二个字节)储存。区位码=区字节+位字节(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
2.2.2 GBK
尽管 GB2312收录了总共6000多个汉字,但是这仍然比较少,还不能覆盖到绝大多数场景的需求。
在1995年,国家出台了GBK(《汉字内码扩展规范》国标扩展的英文简称)规范,GBK1.0收录了21886个汉字,是GB2312的3倍。
GBK 向下与 GB 2312
编码兼容,向上支持 ISO-10646.1
国际标准,在历史的发展中,它是一个对GB2312承上启下的产物。
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,共23940个码位,共收录了21003个汉字,完全兼容GB2312标准。他还能支持日文假名、繁体中文等字符。
GBK也是使用 区字节+位字节的方式存储,而其高字节的从 81-FE,低字节从40-FE,总共有24066个位置。
2.2.3 GB18030
GB18030是2000年国家出台的取代GBK1.0的国标方案,用于补充GBK的字符,所以它是兼容GBK和GB2312
它有两个大的历史版本:
- GB18030-2000
2000年的版本,收录27533个汉字。标准兼容Unicode 3.0中日韩统一表意文字 - GB18030-2005
2005年更新,收录70244个汉字,更新至Unicode 3.1中日韩统一表意文字,并收录了少数名族的文字如朝鲜族、蒙古文、藏文、维吾尔文、彝文等。
GB18030的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。
下面是GB18030的解析规则:
- 单字节
使用0×00(0)至0×7F(127)码位,与ASCII码对应 - 双字节
首字节码位从0×81(129)至0×FE(254),尾字节码位分别是0×40(64)至0×7E(126)和0×80(128)至0×FE(254)。和GBK一样(带扩展)。 - 四字节
四字节部分采用GB/T 11383未采用的0×30(48)到0×39(57)作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0×81308130(2,167,439,664)到0×FE39FE39(4,265,213,497)。其中第一、三个字节编码码位均为0×81(129)至0×FE(254),第二、四个字节编码码位均为0×30(48)至0×39(57)。 - 判断规则
从头开始读取字节,字节范围是0~127则是一个字节表示一个字符。
从头开始读取字节,首字节范围是129-254,判断第二位字节;第二位字节如果是48~57则是四个字节表示一个字符,反之是两个字节表示一个字符。
2.3 Big5
Big5(又称大五码或五大码),它的出现时间是90年代,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。是由台湾那边创建的,主要使用的地区是台湾、香港、澳门这些使用繁体的区域。但它本身不是国家/地区的官方标准,只是行业标准。
Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。“高位字节”使用了0x81-0xFE,“低位字节”使用了0x40-0x7E,及0xA1-0xFE。百度到的分区如下:
2.4 Unicode
Unicode是由ISO组织来设计的,它又称万国码、统一码,第一版公布于1994年,设计原因是统一所有的ASCII扩展、ISO字符集、各个国家的字符系统。
所以它和GB系列是同期的,在90年代,GB系列和Unicode应该是在争字符集的no.1的,在21世纪之后,Unicode更受世界各地所接受,因为它功能太过强大了,比如除了支持欧美、亚洲的字符集,还支持中亚的从右到左的书写体系,所以后期它成为了字符集的第一了。
2.4.1 Unicode
Unicode全称为Universal Multiple-Octet Coded Character Set,简称USC,俗称才是Unicode。
Unicode和GB、大五码不一样,它没有规定字符如何在计算机中存储,它为每种语言中的每个字符设定了统一并且唯一的二进制编码。 也就是说它是抽象,而不是实现。
上面提到,Unicode 没有规定字符对应的二进制码如何存储。以汉字“汉”为例,它的 Unicode 码点是 0xE6B189,对应的二进制数是 (111001101011000110001001),二进制数有 18 位,这也就说明了它至少需要 3 个字节来表示。可以想象,在 Unicode 字典中往后的字符可能就需要 3 个字节或者 4 个字节,甚至更多字节来表示了。
这就导致了一些问题,
- 计算机怎么知道你这个 2 个字节表示的是一个字符,而不是分别表示两个字符呢?
这里我们可能会想到,那就取个最大的,假如 Unicode 中最大的字符用 4 字节就可以表示了,那么我们就将所有的字符都用 4 个字节来表示,不够的就往前面补 0。 - 这样确实可以解决编码问题,但是却造成了空间的极大浪费,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无法接受的。
于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16 两种当前比较流行的编码方式诞生了。当然还有一个 UTF-32 的编码方式,也就是上述那种定长编码,字符统一使用 4 个字节,虽然看似方便,但是却不如另外两种编码方式使用广泛。
2.4.2 UTF-16
UTF-16是Unicode的一个分支,全称 Unicode Transfer Format。16的意思是因为最早是使用两个字节(16byte)来表示所有的字符,但是由于设计师没有考虑字符的数量庞大, 两个字节不足以表示所有字符,所以后面设计成2或4个字节表示一个字符
综上所述,UTF-16是一种变长编码,它由2字节或4字节来表示。来看看它的编码范围:
字节数量 | 16进制编码范围 | UTF-16(二进制) | 10进制编码范围 |
---|---|---|---|
2 | U+0000 - U+FFFF | xxxx xxxx xxxx xxxx - yyyy yyyy yyyy yyyy | 0~65535 |
4 | U+10000 - U+10FFFF | 1101 10yy yyyy yyyy - 1101 11xx xxxx xxxx | 65536-1114111 |
UTF-16大部分的字符都是2字节,所以它能节省不少带宽,不过它不能兼容ASCII码。
2.4.3 UTF-8
UTF-8也是Unicode的一个分支,它也是一种变长编码,使用1~4个字节来表示一个字符,因为1个字节的关系,所以它比起UTF-16可以兼容ASCII码。
它的判断规则为:
- 对于UTF-8编码中的任意字节X,如果X的第一位为0,则X独立的表示一个字符(ASCII码)
- 如果X的第一位为1,第二位为0,则X为一个多字节字符中的后续字节(非第一字节)
- 如果X的前两位为1,第三位为0,则X为两个字节表示的字符中的第一个字节
- 如果X的前三位为1,第四位为0,则X为三个字节表示的字符中的第一个字节
- 如果X的前四位为1,第五位为0,则X为四个字节表示的字符中的第一个字节
可以看下下表:
Unicode 十六进制码点范围 | UTF-8 二进制 | 字节数 |
---|---|---|
0000 0000 - 0000 007F | 0xxxxxxx | 1 |
0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx | 2 |
0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 4 |
可以看到这种编码方式还是挺独特、惊艳的。UTF-8一般不用于计算机硬件编码,更多的是用于网络的数据交换,我们使用Http的传输,使用的数据格式都会支持 UTF-8/UTF-16的。
2.4.4 UTF-32
UTF-32是四字节的定长编码,是最为简单的编码方案,所以它没有上述复杂的判断规则,直接取偏移值查表就行了,即O(1)的时间复杂度,但是同时带宽消耗会挺大了,就是时间换空间这样。
2.5 BaseXX
2.1-2.4的编码都是字符集为主。除此之外,Base系列也是一种常见的编码格式。
2.5.1 Base64
Base64的原理是将二进制数据(非文本数据)转换成由64个字符组成的字符串的编码算法。
64个字符是 a-z A-Z 0-9 + / 组成的字符,有一个专门的码表:
比如 M的ASCII是77,对应的二进制是 01001101。
然后自己规定,每6位截取一下,那么就能 切成 010011 + 01,其中 前半部分换成10进制是19,在码表中对应T,后半部分01换成十进制是1,在码表中对应B。
那么M被Base64转换之后就变成了 TB。
其中最多只能规定6位截取一下,因为码表只有64个字符,如果超出6位,那么多出来的码表就没有了。
Base64的优点是简单。
缺点是不高效,都把数据变得更长了,能不用就不用。
2.5.2 Base58
相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+“和”/"符号。
PS:这个是不是有点像我们国家的车牌号?我国车牌号是没有0、O、大写I和小写l,还有那两个符号的,会不会我们的车牌号就是用Base58来编码的???
参考文章
部门分享会
百度百科
彻底弄懂 Unicode 编码