一.ASCII码到Unicode
ASCII
码是7
位,它将英文字母,数字0-9
以及一些标点符号和控制字符映射为0-127
这些整型。
由于8
位的空间对于各国文字远远不够,因此Unicode
码诞生。
起初,Unicode
被设计为16位,提供65536
个字符的空间。当时人们认为这已经大到足够编码世界上现代文本里所有的文字和字符了。
考虑到历史上的文字和比较少使用到的日本
和中国
汉字,Unicode
编码扩展到21
位。
二.Unicode编码空间
Unicode
的基本元素被称作编码点(Code Point)
。编码点通过数字来区分,通常写成16进制的形式再加上前缀"U+"
,所有编码点组成的集合被称作编码空间(Code Space)
。
Unicode
的编码空间包含1114112
个编码点。然而,其中只有128237
个编码点(12%
的编码空间被赋值),目前,还有很多空间用来增长,同时,Unicode
还保留了另外的137468
字符作为"自用"空间,这些字符没有标准的含义,可以被个人应用所使用。
为了对编码空间的布局有个了解,把它可视化会比较直观。下面是整个编码空间的布局,一个像素代表一个编码点。使用小方块表示以保证视觉的一致性;每个小方块是16*16=256
个编码点,每个大方块是一个面有65536
个编码点,总共加起来有17
个面板。
- 白色空间表示未使用
- 蓝色表示已经使用
- 绿色表示自用区域
- 小的红色表示代理区
第一个面板被称作(基本多语言面板BMP
)。BMP
包含现代文本所需的基本所有字符,包括拉丁文、斯拉夫文、希腊文、汉字(中国),日文、朝鲜文、阿拉伯文、希伯来文、梵文(印度)等。这个面板就是最长Unicode
设计所占用的空间(16
位,65536
个字符).后来扩展到现在这个规模,然而,大部分现代字符在BMP
的范围内。
第二个面板则包括历史上的文字,比如苏美尔楔形文字和埃及象形文字以及emoji
表情.
第三个面板包含了一大块不常用的历史上的汉字字符。
剩下的面板,除了倒数第三个面板中有一小部分被用作格式化字符;倒数两个面板全部保留自用。
为了和以前的ASCII
兼容,Unicode
的128
个字符就是ASCII
的拷贝.
三. UTF8,UTF16,UTF32
这是unicode
编码面板中的前三个面板的使用频率图,可以看出使用频率最高的绝大多数分部在BMP
内,零散的来自第二三个面板。第二个面板下高频率使用的字符则是部分emoji
表情。
为了解决unicode
编码占据的内存问题,unicode
就有了几个紧凑的编码。
注意
UTF是Unicode Transformation Format的缩写,意为Unicode转换格式
其中,UTF-8是UTF中最常用的转换格式,是UNICODE的一种变长字符编码。
UTF-32
32
位整数编码,很少被用来存储,因为太占用内存和存储空间。
UTF-8/UTF-16
这两个编码是可变长编码,分别由8-bit
或16-bit
为一个单元组成,这些方案中下标值较小的编码点占用的字节数也少,会节省不少内存。
1.UTF-8
在UTF-8
中,每个编码点依据下标值,被存储为1
到4
个字节。
越是常用的字符,字节越短,最前面的128
个字符,只使用一个字节表示,与ASCII
码完全相同。
UTF8
有以下几个好处:
对于很常见的西文字符,采用这种编码方式也不会浪费内存。
由于
UTF-8
是基于8
位的码元的,因此它并不需要关心字节顺序。任何已经是
ASCII
编码的字符串和文件无需转换就可以被UTF-8
识别。大量的广泛使用的编程惯例——比如
NULL
结尾,分隔符(n,t,’,’,”)
等——在UTF-8
中也是可用的。
因为这些原因,UTF-8
成为存储和交流 Unicode
文本方面的最佳编码。它也已经是文件格式、网络协议以及 Web API
领域里事实上的标准了。
2.UTF16
UTF-16
编码介于UTF-32
与UTF-8
之间,同时结合了定长和变长两种编码方法的特点。
它的编码规则很简单:基本平面的字符占用2
个字节,辅助平面的字符占用4
个字节。也就是说,UTF-16
的编码长度要么是2
个字节(U+0000
到U+FFFF
),要么是4
个字节(U+010000
到U+10FFFF
)。
于是这里就存在一个问题,当我们遇到两个字符,怎么看出它本身是一个字符,还是需要跟其他两个字节放在一起解读。
我们可以从基本平面看到,从U+D800
到U+DFFF
是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。
具体来说,辅助平面的字符位共有2^20
个,也就是说,对应这些字符至少需要20
个二进制位。UTF-16
将这20
位拆成两半,前10
位映射在U+D800
到U+DBFF
(空间大小2^10
),称为高位(H),后10
位映射在U+DC00
到U+DFFF
(空间大小2^10
),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。
所以,当我们遇到两个字节,发现它的码点在U+D800
到U+DBFF
之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00
到U+DFFF
之间,这四个字节必须放在一起解读。
3. UTF-32
UTF-32
,将Unicode
的每个码点使用4
个字节表示,字节内容一一对应码点。比如,码点0
就用4
个字节的0
表示,码点597D
就在前面加两个字节的0
。
U+0000 = 0x0000 0000
U+597D = 0x0000 597D
UTF-32
的优点在于,转换规则简单,查找效率高,缺点在于浪费空间,同样内容的英语文本,会比ASCII
码大四倍。
这个缺点导致实际上没有人使用这种编码方法。
四.组合字符
Unicode
包含一个系统,可以合并多个编码点,动态组合字符。此系统用这种方式增加灵活性,而不引起编码点的巨大组合膨胀。
例如,带重音的字符“Á”
会被表示成由两个编码点组成的字符串:U+0041 “A”
拉丁大写字母 a
加上 U+0301 “◌́”
组合尖音符号。这个字符串自动被渲染成单个字符:“Á”
。
如今,Unicode
还包含许多 “预设的” 编码点,每个表示一个被使用过的组合,例如 U+00C1 “Á”
带锐音符的拉丁大写字母A
或 U+1EC7 “ệ”
带扬抑符和下点的小写拉丁字母 e
。
Unicode
中,预设字符和动态组合系统并存。后果就是有多种方法表示同一个字符串——不同编码点序列产生相同用户可感知的字符。例如,我们之前看到的,表示字符 “Á”
,我们可以用一个编码点 U+00C1
,也可以用两个编码点 U+0041
和U+0301
。要解决这个等值字符串的问题,Unicode
定义了几种形式正规化方法。比如NFD
和NFC
。
五.字符簇
Unicode
包含多种情况,用户认为的一个"字符"事实上底下可能由多个编码点组成。
Unicode
使用"字位簇"
的概念来表示这种情况,一个由一个或多个编码点组成的字符串构成一个"用户感知的字符"。
部分的emoji
的unicode
长度大于1
的本质原因是这些emoji
是字符簇。
从这里我们可以知道emoji
表情其实是由一个或多个编码点组成的字符串,那我们要怎么判断用户输入的是否为emoji
表情,又或者判断一个字符串中是否包含emoji
表情呢。
具体详见下一篇: