从一个问题说起
为什么计算字符串的长度的时候会把中文当做2个字符,英文当成一个字符来计算?
基本概念
字符
在计算机中,字符是一个信息单位。简单来说就是一个汉字或者一个英文,一个标点符号。
字符集
顾名思义,字符集就是字符的集合。
字符编码
字符编码就是把字符集中的字符编码为指定集合中的某一对象,以便在计算机中存储和通过网络传递。
码元
是指一个已编码的文本中具有最短比特组合的单元。对于UTF-8来说码元是8比特长。对于UTF-16来说,码元是16比特长。对于UTF-32来说,码元是32比特长。
字符编码由以下几个关键元素构成:
- 抽象字符表(Abstract character repertoire): 是一个系统支持的所有抽象字符的集合,简单点说就是有哪些字符。
- 编码字符集(Coded Character Set): 是将字符集 C中每个字符映射到1个坐标(整数值对:x, y)或者表示为1个非负整数 ,简单点说就是字符的编号。
- 字符编码表(Character Encoding Form): 是将编码字符集中的非负整数值转换成有限比特流长度整型值(码元)的序列。简单点说就是这些编号如何编码成一系列的码元。
- 字符编码方案(Character Encoding Scheme): 这些单元如何组成八位子节流。 比如在Unicode的场合,使用一个简单的字符来制定字节的顺序是大端序或者小端序。(UTF-8不需要专门指明字节序) 有些复杂的字符编码机制(如ISO/IEC 2022)使用控制字符转义序列在几种编码字符集或者用于减小每个单元所用的字节数的压缩机只之间切换
首先了解一下字符编码的历史
ASCII码-1967
全称是American Standard Code for Information Interchange,译做美国信息交换标准代码。这套字符集在1967年被正式公布。
它一共包含128种字符,其中33个控制字符,95个可显示的字符。正好是一个字节的低7位,最高位置0。
下图是一个ASCII码的表格:
EASCII码 与 ISO 8859 - 1985
刚开始计算机只在美国使用,所以128个字符是够用的,但是随着科技的发展欧洲的国家也开始使用上了计算机。这时候出现了问题,128个字符不够用了。比如法语中有注音符号,于是一些欧洲国家决定利用字节中闲置的最高位编入新的符号。 这样就在ASCII码的基础上,既保证了对ASCII码的兼容性,又补充扩展了新的字符,于是就称之为Extended ASCII(扩展ASCII)码,简称EASCII码。
在EASCII码中,当第一个比特位(即字节的最高位)为0时,仍表示之前那些常用的ASCII字符(实际的二进制编码为0000 0000 ~ 0111 1111,对应的十进制就是0~127),而为1时就表示补充扩展的其他衍生字符(实际的二进制编码为1000 0000 ~ 1111 1111,对应的十进制就是128~255)。
EASCII的扩展部分如下图: [image:DA4EFEE5-575B-4D69-AB85-0D6A0963052E-55654-00029E64D82D4B45/18408A67-1AF3-4473-AE8A-E7E6DE1C355B.png]
但是有这样想法的国家有很多,所以还有另一套标准就是ISO 8859。
ISO 8859 是一组字符集的总称,其下共包含了15个字符集,即ISO 8859-n ,其中n=1,2,3…11,13,14,15,16
这两种编码方式都是前128位兼容ASCII码,后128位自己定义。
GB2312 - 1981
后来计算机进入了中国,EASCII码也不够用了。要知道,汉字是世界上包含符号最多的文字。于是⌈中国国家标准总局⌋(现已更名为⌈国家标准化管理委员会⌋)在1981年,正式制订了中华人民共和国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,项目代号为GB 2312 或 GB 2312-80(GB为国标汉语拼音的首字母),此套字符集于当年的5月1日起正式实施。
GB2312编码为了避免与ASCII字符编码(0~127)相冲突,规定表示一个汉字的编码(即汉字内码)的字节其值必须大于127(即字节的最高位为1),并且必须是两个大于127的字节连在一起来共同表示一个汉字(GB2312为双字节编码),前一字节称为高字节,后一字节称为低字节;而一个字节的值若小于127(即字节的最高位为0),自然是仍表示一个原来的ASCII字符(ASCII为单字节编码)。
因此,可以认为GB2312是对ASCII的中文扩展(即GB2312与ASCII相兼容),正如EASCII是对ASCII的欧洲文字扩展一样。
不过,很显然的是,GB2312与EASCII码的128~255这段扩展部分所表示的字符是不同的。也就是说,GB2312与EASCII虽然都兼容ASCII,但GB2312并不兼容EASCII的扩展部分。
事实上,目前世界上除ASCII之外的其它通行的字符编码方案,基本上都兼容ASCII,但相互之间并不兼容。
GB2312中,可能是出于显示上视觉美观的考虑,除汉字之外的682个字符中,甚至包括了ASCII里本来就有的数字、标点、字母等字符。也就是说,这些ASCII里原来就有的单字节编码的字符,又再编了两个字节长的GB2312编码版本。这682个字符就是常说的“全角”字符,而这682个字符中所对应的ASCII字符就被称之为“半角”字符。
Unicode
上面说了这么多种编码,每个国家都有自己的编码标准,以便能在计算机上正确的显示自己国家的符号。但是不同国家地区互相不能正确显示。所以有2个组织来尝试指定统一的编码标准。
- 国际标准化组织(ISO)
- 统一码联盟 国际标准化组织(ISO)及国际电工委员会(IEC)于1984年联合成立了ISO/IEC小组,主要用于开发统一编码项目; 而Xerox、Apple等软件制造商则于1988年组成了统一码联盟,用于开发统一码项目。 两个组织都在编写统一字符集,但后来他们发现各自在做相同的工作,同时世界上也不需要两个不兼容的字符集,于是两个组织就此合并了双方的工作成果,并为创立一个单一编码表而协同工作。
1991年,两个组织共同的工作成果Unicode 1.0正式发布,不过Unicode 1.0并不包含CJK字符(即中日韩)。
GB13000 - 1993
1993年时,包含CJK的Unicode 1.1已经发布了,于是在同一年,中国大陆制定了几乎等同于Unicode1.1的GB13000.1-93国家编码标准(简称GB13000)。是的,你没听错,中华人民共和国信息产业部把Unicode里的所有东东拿过来,然后自己重新修订发布了下,改为了国家标准GB13000。此标准等同于 ISO/IEC 10646.1:1993和Unicode 1.1。
GBK - 1995
GB2312 基本覆盖了中国大陆99%以上的使用频率,基本满足了汉字的计算机处理需要。但对人名,生僻字等并不能处理。
于是,利用GB2312未使用的码点空间,收录这些字符,于1995年又发布了《汉字内码扩展规范(GBK)》。其中K是扩展的意思。
GBK跟GB2312一样是双字节编码,然而,GBK只要求第一个字节即高字节是大于127就固定表示这是一个汉字的开始(0~127当然表示的还是ASCII字符),不再要求第二个字节即低字节也必须是127号之后的编码。这样,作为同样是双字节编码的GBK才可以收录比GB2312更多字符。
所以GB系列的编码方案中,一个汉字是由2个字节组成,一个英文是由一个字节组成。
接下来详细讨论一下Unicode的编码方案
USC-2 和 USC-4
顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码
UTF-8
是Unicode的一种实现方式。是一种可变长度的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号来变化字节长度。 编码规则:
- 对于单字节符号,字节的第一位设为0,后面7位为这个符号的Unicode码
- 对于n(n>1)字节的符号,第一个字节的前n位都设为1,第n+1位设为0,后面的字节得前2位一律设为10。剩下的二进制位全部为这个符号的Unicode码。
编码实现:
根据编码规则,同理可以推出解码规则:
举个例子: 0xxxxxxx 110xxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx 1110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-16
也是Unicode的一种实现方式。是把Unicode字符集的抽象码位映射为16位长的整数(码元)的序列。需要1~2个16位长的码元来表示,所以这是一个边长表示。
Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符。Unicode的编码空间可以划分为17个平面(Plane),每个平面包含2^16(65536)个码位。17个平面的码位可表示为从U+xx0000 到 U+xxFFFF,其中xx表示十六进制从00到10。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区块是永久保留不映射到Unicode字符。UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。
编码规则: U+0000 ~ U+D7FF 和 U+E000 ~ U+FFFF 这个范围即基本多语言平面(Basic Multilingual Plane, BMP),包含了最常用的字符,包含的码位范围是U+0000 到 U+FFFF,只需要一个16位的码元即可表示。
U+10000 ~ U+10FFFF 其它平面(叫做辅助平面,Supplementary Planes)中的码位,在UTF-16中被编码为一对16比特长的码元(即32bit,4Bytes),称作代理对(surrogate pair),具体方法是:
- 码位(0x10000~0x10FFFF)减去0x10000(BMP范围),剩下20比特长的数字,范围是0..0xFFFFF。
- 高位的10比特(数值范围为0..0x03FF)被加上0xD800得到第一个16位长的码元或称作高位代理(high surrogate),值的范围是0xD800..0xDBFF。
- 低位的10比特的值(值的范围也是0..0x3FF)被加上0xDC00得到第二个16位长的码元或称作低位代理(low surrogate),现在值的范围是0xDC00..0xDFFF。
上述算法可理解为:辅助平面中的码位从U+10000到U+10FFFF,共计FFFFF个,即2^20=1,048,576个,需要20位来表示。如果用两个16位长的整数组成的序列来表示,第一个整数(称为前导代理)要容纳上述20位的前10位,第二个整数(称为后尾代理)容纳上述20位的后10位。还要能根据16位整数的值直接判明属于前导整数代理的值的范围(2^10=1024),还是后尾整数代理的值的范围(也是2^10=1024)。因此,需要在基本多语言平面中保留不对应于Unicode字符的2048个码位,就足以容纳前导代理与后尾代理所需要的编码空间。这对于基本多语言平面总计65536个码位来说,仅占3.125%.
编码规则实现:
根据UTF-16的编码规则,同理可以得到解码规则,下图是从Swift源码中截取的UTF-16的解码代码: [image:69D26150-1CDC-4E9D-B94E-916136C27353-55654-0002535098FC388A/46208888-62B3-4137-95AE-A697AFD5B43A.png]
举个例子: 例如U+10437编码(?)
- 0x10437减去0x10000,结果为0x00437,二进制为0000 0000 0100 0011 0111。
- 分区它的上10位值和下10位值(使用二进制):0000000001 and 0000110111。
- 添加0xD800到上值,以形成高位:0xD800 + 0x0001 = 0xD801。
- 添加0xDC00到下值,以形成低位:0xDC00 + 0x0037 = 0xDC37。
UTF-16比起UTF-8,好处在于大部分字符都以固定长度(2字节)存储,但是UTF-16无法兼容ASCII码。
思考问题:
1、Unicode与UTF-8 16 有什么区别?
2、为什么UTF-8不需要专门指明字节序?
3、为什么计算字符个数会把英文当成一个中文当成两个?
4、我们常说的Unicode编码指的是什么?