字符编码是人类和计算机交流的桥梁
计算机只认识二进制字符(0和1),这是计算机的设计所决定的。而人类的语言字符要想在计算机中传输的话就必须转换为二进制字符。早期的计算机也只是用来处理数字,把现实中的十进制数字转换为计算机的二进制字符进行处理。随着需求的需要,计算机必须能够支持文本、音频、视频等形式的信息。所以,就必须把这些文本、音频、视频转换为二进制数字来交给计算机处理。需要一个统一的规范来把这些信息转换为二进制字符。于是就有了ASCII码、Unicode码等规范。
ASCII码
早期的计算机主要在美国流行,为了解决统一规范的问题,美国有关的标准化组织就出台了ASCII编码,统一规定了常用符号用哪些二进制数来表示。
ASCII码一共规定了128个字符(0-127号)的编码,包括32个控制字符和96个打印字符。
96个打印字符包括52个英文字母(大小写)、10个阿拉伯数字以及一些标点符号等字符
这128个符号,只占用了一个字节的后面7位,最前面的一位统一规定为0
一个字节有八位,每一位有0和1两种状态。所以一个字节有256种状态
ASCII码如下
由上表可知:大写的字母A是65(二进制01000001)
unicode码
随着国际互联网的迅速发展,进行数据交换的需求越来越大,于是就出现了各种各样的编码体系,而不同的编码体系之间并不兼容,成为信息交换的障碍。
一些表音文字系统的语言把ASCII码进行了拓展,将闲置的高位利用起来。128-255号字符,不同的国家有不同的编码体系,无法统一
对于汉语这种表意文字系统,那就完全不够用。开玩笑,256个位置全让出来都不够我做两句诗。于是就有了GB2312、GBK等这些编码
为了包罗所有语言字符,unicode码应运而生。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。因此,unicode码简称为UCS,又被称为统一码、万国码、单一码。
unicode(UCS)分为两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码(有2^16 = 65536个码位,支持大部分常用的汉字),UCS-4就是用4个字节(实际上只用了31位,最高位必须为0。有2^31 = 2147483648个码位)编码。
任何文字在Unicode中都对应一个代码点(code point),以十六进制表示,代码点格式如下
U+xxxx //U+为unicode的标志,xxxx是十六进制数
U+9ad8(程序里写做\u9ad8)代表汉字的'高'
UCS-2 取值范围为U+0000~U+FFFF
UCS-4 取值范围为U+00000000~U+7FFFFFFF
UCS-4 中 U+00000000~U+0000FFFF和UCS-2是一样的
USC-2所代表的字符以及十进制表示
UCS-2码点范围(十六进制) | 十进制 | 说明 |
---|---|---|
U+0000 - U+007F | 0-127 | ASCII码包含的字符 |
U+0080 - U+00FF | 128-255 | ASCII码扩展部分包含的字符 |
U+4E00 - U+9FA5 | 19968-40895 | CJK(中日韩)统一表意符号 |
由于Unicode码只是字符集,规定了语言字符和二进制字符之间的对应关系。但没有规定字符对应的二进制码如何存储。而一个字符可能需要几个字节来表示(比如'汉这个字符',它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,需要2个字节来表示)。这就导致了一些问题,计算机怎么知道你这2个字节表示的是一个字符,而不是分别表示两个字符呢?
一种解决方案是将所有的字符都用4个字节(4字节能表示世界上绝大多数的字符)来表示,不够的就往前面补0(这种方案叫做UTF-32编码,是定长的编码)。这样确实可以解决编码问题,但是却造成了空间的极大浪费,如果是一个英文文档,那文件大小就大出了3倍,这显然是无法接受的。
于是,为了较好的解决Unicode的编码问题, UTF-8 和 UTF-16 两种当前比较流行的编码方式诞生了。
UTF-8编码
UTF是Unicode Transformation Format
的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。
UTF-8 是目前互联网上使用最广泛的一种 Unicode 编码方式,它的最大特点就是可变长
。它可以使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。
UTF-8中的8指的是字符以8位序列来编码的。用一个或几个字节来表示一个字符。这种方式的最大好处是UTF-8保留了ASCII字符的编码做为它的一部分(0x00-0x7F之间的字符),即UTF-8编码兼容ASCII编码。
UTF-8编码方式只有两条规则
//单字节
对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。
//多字节
对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设为 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。
图表表示如下
UCS-2编码(十六进制) | UCS-4编码((十六进制) | UTF-8编码(二进制) |
---|---|---|
0000-007F | 000000-00007F | 0xxxxxxx |
0080-07FF | 000080-0007FF | 110xxxxx 10xxxxxx |
0800-FFFF | 000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
01代表的位置是UTF8的元数据,x代表的位置才是Unicode的数据
从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
语言字符转换为UTF-8编码
以汉字字符高
为例
高
字的unicode编码是U+9ad8(unicode码表),对应的二进制为1001101011011000(在线进制转换工具)
根据上表可知9ad8在0800-FFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将高
字的二进制码依次填入3字节模板的x中,得到高
字的UTF-8编码为 111010011010101110011000,对应的十六进制为e9ab98。
所以,高
字对应的unicode码为9ad8(二进制1001101011011000);对应的UTF-8编码为e9ab98(二进制为111010011010101110011000)
UTF-8编码转换为语言字符
从第一个byte开始读,数第一个0出现之前的1,有几个1就代表前面几个byte是一组的,0个1就代表当前的这个byte孤家寡人一个。然后跳过这个组的所有byte,继续之前数1的环节。分好组后,按组找到上表右边的规则,把规则内x的位置保留下来,01的位置全部扔掉(01代表的位置是UTF8的元数据,x代表的位置才是Unicode的数据),拼成新的二进制串,这个串就是Unicode了。
以UTF-8编码:11100101 10001100 10111010 11100101 10011101 10010111 11101001 10010011 10111110 为例
以上有9个byte,我们从第一个byte11100101开始,数第一个0前面的1的数量,有3个1,代表3个byte是一组的。然后我们跳过这3个,第四个byte是11100101,继续数1得出有3个1,然后又给这3个byte分组。跳过这三个,到了第7个byte11101001,继续数1得出有3个1,然后又给这3个byte分组。现在我们分好组了,有3个组,分别是
[11100101 10001100 10111010], [11100101 10011101 10010111], [11101001 10010011 10111110]
现在我们按组找到表右对应的行,三组对应3字节模板,我们把行内x对应的位置保留,10的位置删除,得到新的数组
[0101, 001100, 111010], [0101, 011101, 010111], [1001,010011,111110]
然后把组内的二进制串起来得到Unicode
[0101001100111010], [0101011101010111], [1001010011111110]
转换为十六进制为533a、5757、94fe(在线进制转换工具)
查询unicode码表可知,对应的字符为区块链
总结
字符集:为每一个「字符」分配一个唯一的码点(Code Point)
编码规则:将「码点」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)
字符集和字符编码的出现都是为了人类的语言字符与计算机的二进制字符匹配问题。
计算机只认识二进制字符(0和1),这是计算机的设计所决定的。而人类的语言字符要想在计算机中传输的话就必须转换为二进制字符。
早期的时候字符集和字符编码没有严格的区分,像ASCII码,基本上都是将字符集里的字符进行编号(字符编号转化为二进制数后只占用一个字节),然后该字符编号就是字符的编码。所以ASCII字符集与ASCII编码其实是一回事。
而后来需要用到若干个字节,字符集和字符编码不能表示同样的意思了。比如unicode字符集(也叫unicode码、UCS)和UTF-8字符编码
ASCII码出现时间早,只需要一个字节,适用于匹配英文字符。unicode码需要若干个字节,适用于匹配世界上任何语言的字符。
unicode又称为UCS,以十六进制表示。分为两种格式:UCS-2和UCS-4。目前主要使用UCS-2
格式如下
U+xxxx //U+为unicode的标志,xxxx是十六进制数
U+9ad8(程序里写做\u9ad8)代表汉字的'高'
UCS-2 取值范围为U+0000~U+FFFF
UCS-4 取值范围为U+00000000~U+7FFFFFFF
UCS-4 中 U+00000000~U+0000FFFF和UCS-2是一样的
为了区分多字节表示的unicode字符到底是表示一个多字节的字符,还是多个一字节的字符,出现了UTF-8、UTF-16、UTF-32等编码方式(ASCII码就没有这个问题,因为它只需要一个字节)。普及最好的是UTF-8编码方式。UTF-8编码兼容ASCII码
unicode码与UTF-8转换规则如下
UCS-2编码(十六进制) | UCS-4编码((十六进制) | UTF-8编码(二进制) |
---|---|---|
0000-007F | 000000-00007F | 0xxxxxxx |
0080-07FF | 000080-0007FF | 110xxxxx 10xxxxxx |
0800-FFFF | 000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
//单字节
对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。
//多字节
对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设为 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。