Unicode
Unicode,又称万国码、统一码和国际码,是由统一码联盟制定的一套规范统一的字符编码集,其设计意图是将世界上所有字符都包含在其中,它使用特定的十六进制编号来表示字符,每一个特定十六进制编号统称为码点,也叫码位,用“U+”紧接四位或五位十六进制数字来进行表示(例如U+597D表示中文“好”),在Unicode3.0中,也可用“U-”紧接八位十六进制数字表示。目前,Unicode整理并编码了世界上绝大部分的字符,并且已经普遍运用到各个编程语言,其中就包括了接下来要讲的JavaScript编码。
Unicode的最新版本是2020年3月发布的13.0,其中共收录了143,924个符号。当前实际应用的统一码版本是使用16位二进制的编码空间,也就是每个字符占用两个字节,理论上最多可表示216(即65536)个字符,其范围是0 ~216-1,写成十六进制就是U+0000 ~ U+FFFF,这基本满足当前各语言的日常使用,要注意的是16位编码空间也并未完全用于字符编码,其中留有相当一部分作为特殊作用或者后续扩展,例如后面要说的 “ 辅助区间 ”,上述16位编码空间被称为 “ 基本平面 ”,但最新版本扩展定义了其余16个“辅助平面”,其码点范围是U+010000 ~ U+10FFFF,用于收录更多的字符,每个平面可编码字符数量理论上与基本平面一致,所以17个平面至少需要占据21位编码空间,可收录字符数量理论上为221个,略小于三个字节,但实际上却是使用四个字节来存储辅助平面的字符,其目的主要是便于未来版本的扩充以及和其它字符集进行融合。
上述为Unicode做了个详细介绍,但是本质上只说明了Unicode的编码方式,而在实际传输或者存储的过程中,可能由于系统或者平台的差异或者出于空间节省的目的,在实现的方式上有所不同。简而言之
Unicode就是一本用指定一组特定二进制序列表示字符的字符典籍,它只制定字符的对应二进制序列,但没说明该字符二进制序列要以何种方式进行传输或存储。
Unicoded的实现方式就是常讲的Unicode转换格式(Unicode Transformation Format,简称为UTF),常见的Unicode转换格式有UTF-8、UTF-16、UTF-32,当然还有其它,这里就暂不讲述,以下各自讲一下这三种转换格式的特点和转码方式。
UTF-8
UTF-8,英语全称是8-bit Unicode Transformation Format,是一种针对Unicode的可变长字符编码,也是当前使用率最高和应用最广泛的转码方式。因为Unicode较小码点的使用频率较高,如果直接使用Unicode编码则会效率低下,并浪费大量内存空间,而本身UTF-8就是为了解决向后兼容ASCII编码而设计的,Unicode的前128个字符的二进制编码与ASCII码一一对应,所以为了节省空间,提高编码效率,UTF-8根据自己制定的编码规则,使用一至四个字节对Unicode所有的有效码点进行编码。
关于Unicode转换为UTF-8的编码规则其实很好理解,只有两条
下表总结了编码规则,字母x
表示可用编码的二进制位
Unicode符号范围(十六进制) |
UTF-8编码方式(二进制) |
0000 0000-0000 007F |
0xxxxxxx |
0000 0080-0000 07FF |
110xxxxx 10xxxxxx |
0000 0800-0000 FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
下面以汉字 “ 严 ” 为例,演示如何实现 UTF-8 编码。
严的 Unicode 是U+4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是0xE4B8A5。
同样,解码UTF-8二进制序列的过程也很简单。当拿到一串经UTF-8编码后的字节序列时,对于其中的任意字节,如果第一位是0,则表示该字节是单字节字符(ASCII码);如果第一位是1,第二位是0,则表示该字节是多字节字符中的一个字节(非ASCII码);如果前两位是1,第三位是0,则表示该字节是两个字节表示的字符中的第一个字节,则和与其紧接的下一个字节组成该字符的字节编码。后面三字节和四字节表示的字符的识别规则同理。
我们还是以汉字 “ 严 ” 为例,简单实现UTF-8解码。
首先,我们收到一串二进制序列11100100 10111000 10100101,也知道它的转换格式是UTF-8,开始识别第一个字节可以知道后面两个字节都是某个字符的组成字节,根据上面表格,第一个字节前四位,后两字节前两位都是标识位,所以不提取,只提取非标识位,得到二进制序列100 111000 100101,其十六进制为0x4E25,对照Unicode码点表,与之相同码点对应的字符正好是汉字“严”。
至此,UTF-8的介绍就到这里。
UTF-16
UTF-16,全称16 bits Unicode/UCS Transformation Format,是Unicode字符编码五层次模型的第三层,也就是字符编码表(Character Encoding Form,简称CEF,也称为"storage format")的一种实现方式,它将Unicode字符集的抽象码位映射为16位长的整数(即码元,指有限大小的数字,此处UTF-16的16指的就是码元为16位)的序列,简单的说就是将字符码点用一个或多个码元表示。而对于Unicode字符集来说,一个码元表示所有字符是远远不够的,所以超出一个码元表示范围的字符都是用两个码元组成来表示的,所以实质上UTF-16也是一种变长字符编码。这和UTF-8有很大的不同,UTF-16的最小表示单位是两字节,而UTF-8是一个字节,所以UTF-16是无法兼容ASCII。
上面说过,码点范围U+0000 ~ U+FFFF是Unicode的基本平面,并且在这个区间内并不是每个码点都映射字符,而是留有一部分区间另有它用。Unicode标准规定,从U+D800到U+DFFF之间的码位区段是永久保留不映射到Unicode字符,而这个区间则用于对辅助平面的字符的码位进行编码,这段区间叫做代理区间。
我们先简单说一下UTF-16的编码规则,基本平面和辅助平面的编码方式是不一样的。
Unicode第一个平面,也就是基本平面,恰好是一个码元空间,所以在该区间中的字符编码成UTF-16时,其UTF-16编码就是该字符的Unicode码点编码。
Unicode辅助平面在UTF-16中被编码成一对16比特长的码元(32位,4字节),其被称为“代理对”。具体编码规则如下
辅助平面的十六进制区间范围为0x010000 ~ 0x10FFFF,分别减去0x10000,得到值的范围是0 ~ 0xFFFFF,所以可以知道辅助平面的码位有220(1,048,576)个,而要完整表示该区间每个值需要20个二进制位。
UTF-16将这20位拆成两半,前10位加上0xD800,映射在0xD800~0xDBFF(空间大小2^10),称为高位代理(high surrogate);后10位加上0xDC00,映射在0xDC00~0xDFFF(空间大小2^10),称为低位代理(low surrogate),但是实质上高位代理的值要小于低位代理的值,所以为了避免混淆,高位代理也称为前导代理,低位代理也称为后尾代理。
此后意味着,一个辅助平面的字符,可以被拆成两个基本平面的字符表示,并进行相应的传输和存储。
这里有个地方要了解一下。如果我们用两个16位长码元序列来表示辅助平面中的220个字符,则第一个码元(即前导代理)要容纳上述20位中的前10位,第二个码元(即后尾代理)要容纳上述20位中的后10位,可以知道前导代理和后尾代理所能表示的个数都为210,也就是1024,故需要在基本平面中保留2048个不映射任何Unicode字符的码位才能符合需求,这也刚好是U+D800~U+DFFF区间的大小。
补充一个码元所表示字符是否在基本平面的判定规则。当接收到一连串经UTF-16编码后的字节序列时,会先读取第一个码元,也就是前16位,假如不在代理区间内,那说明该码元对应的字符码位在基本平面内。假如第一个码元在代理区间内,则可以断定该字符码位在辅助平面中,接下来则判断该码元的值是在高位代理的值范围还是在低位代理的值范围之内,如果在高位代理的值范围之内,则表示该码元是表示某字符的码元序列中的前一个码元,则与之紧接的下一个码元也是表示该字符的码元序列中的后一个。
下面以码位U+10437(
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)