C语言:Unicode字符集
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语⾔:Unicode字符集
Unicode 也称为统⼀码、万国码;看名字就知道,Unicode 希望统⼀所有国家的字符编码。
Unicode 于 1994 年正式公布第⼀个版本,现在的规模可以容纳 100 多万个符号,是⼀个很⼤的集合。
有兴趣的读取可以转到查看 Unicode 包含的所有字符,以及各个国家的字符是如何分布的。
Windows、Linux、Mac OS 等常见操作系统都已经从底层(内核层⾯)开始⽀持 Unicode,⼤部分的⽹页和软件也使⽤
Unicode,Unicode 是⼤势所趋。
不过由于历史原因,⽬前的计算机仍然安装了 ASCII 编码以及 GB2312、GBK、Big5、Shift-JIS 等地区编码,以⽀持不使⽤ Unicode 的软件或者⽂档。
内核在处理字符时,⼀般会将地区编码先转换为 Unicode,再进⾏下⼀步处理。
Unicode 字符集是如何存储的
本节我们多次说 Unicode 是⼀套字符集,⽽不是⼀套字符编码,它们之间究竟有什么区别呢?
严格来说,字符集和字符编码不是⼀个概念:
字符集定义了字符和⼆进制的对应关系,为每个字符分配了唯⼀的编号。
可以将字符集理解成⼀个很⼤的表格,它列出了所有字符和⼆进制的对应关系,计算机显⽰⽂字或者存储⽂字,就是⼀个查表的过程。
⽽字符编码规定了如何将字符的编号存储到计算机中。
如果使⽤了类似 GB2312 和 GBK 的变长存储⽅案(不同的字符占⽤的字节数不⼀样),那么为了区分⼀个字符到底使⽤了⼏个字节,就不能将字符的编号直接存储到计算机中,字符编号在存储之前必须要经过转换,在读取时还要再逆向转换⼀次,这套转换⽅案就叫做字符编码。
有的字符集在制定时就考虑到了编码的问题,是和编码结合在⼀起的,例如 ASCII、GB2312、GBK、BIG5 等,所以⽆论称作字符集还是字符编码都⽆所谓,也不好区分两者的概念。
⽽有的字符集只管制定字符的编号,⾄于怎么存储,那是字符编码的事情,Unicode 就是⼀个典型的例⼦,它只是定义了全球⽂字的唯⼀编号,我们还需要 UTF-8、UTF-16、UTF-32 这⼏种编码⽅案将 Unicode 存储到计算机中。
Unicode 可以使⽤的编码⽅案有三种,分别是:
UTF-8:⼀种变长的编码⽅案,使⽤ 1~6 个字节来存储;
UTF-32:⼀种固定长度的编码⽅案,不管字符编号⼤⼩,始终使⽤ 4 个字节来存储;
UTF-16:介于 UTF-8 和 UTF-32 之间,使⽤ 2 个或者 4 个字节来存储,长度既固定⼜可变。
UTF 是 Unicode Transformation Format 的缩写,意思是“Unicode转换格式”,后⾯的数字表明⾄少使⽤多少个⽐特位(Bit)来存储字符。
1) UTF-8
UTF-8 的编码规则很简单:
如果只有⼀个字节,那么最⾼的⽐特位为 0,这样可以兼容 ASCII;
如果有多个字节,那么第⼀个字节从最⾼位开始,连续有⼏个⽐特位的值为 1,就使⽤⼏个字节编码,剩下的字节均以 10 开头。
具体的表现形式为:
0xxxxxxx:单字节编码形式,这和 ASCII 编码完全⼀样,因此 UTF-8 是兼容 ASCII 的;
110xxxxx 10xxxxxx:双字节编码形式(第⼀个字节有两个连续的 1);
1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(第⼀个字节有三个连续的 1);
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(第⼀个字节有四个连续的 1)。
xxx 就⽤来存储 Unicode 中的字符编号。
下⾯是⼀些字符的 UTF-8 编码实例(绿⾊部分表⽰本来的 Unicode 编号):
字符字母N符号æ中⽂⻬
Unicode 编号(⼆进制)010011101110011000101110 11101100
Unicode 编号(⼗六进制)4E E62E EC
UTF-8 编码(⼆进制)0100111011000011 1010011011100010 10111011 10101100
UTF-8 编码(⼗六进制)4E C3 A6E2 BB AC
对于常⽤的字符,它的 Unicode 编号范围是 0 ~ FFFF,⽤ 1~3 个字节⾜以存储,只有及其罕见,或者只有少数地区使⽤的字符才需要 4~6
个字节存储。
2) UTF-32
UTF-32 是固定长度的编码,始终占⽤ 4 个字节,⾜以容纳所有的 Unicode 字符,所以直接存储 Unicode 编号即可,不需要任何编码转换。
浪费了空间,提⾼了效率。
3) UTF-16
UFT-16 ⽐较奇葩,它使⽤ 2 个或者 4 个字节来存储。
对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使⽤两个字节存储,并且直接存储 Unicode 编号,不⽤进⾏编码转换,这跟UTF-32 ⾮常类似。
对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使⽤四个字节存储,具体来说就是:将字符编号的所有⽐特位分成两部分,较⾼的⼀些⽐特位⽤⼀个值介于 D800~DBFF 之间的双字节存储,较低的⼀些⽐特位(剩下的⽐特位)⽤⼀个值介于 DC00~DFFF 之间的双字节存储。
如果你不理解什么意思,请看下⾯的表格:
Unicode 编号范围(⼗六进制)具体的 Unicode 编号
(⼆进制)
UTF-16 编码编码后的
字节数
0000 0000 ~ 0000 FFFF xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx2
0001 0000---0010 FFFF yyyy yyyy yyxx xxxx xxxx110110yy yyyyyyyy 110111xx xxxxxxxx4
位于 D800~0xDFFF 之间的 Unicode 编码是特别为四字节的 UTF-16 编码预留的,所以不应该在这个范围内指定任何字符。
如果你真的去查看 Unicode 字符集,会发现这个区间内确实没有收录任何字符。
UTF-16 要求在制定 Unicode 字符集时必须考虑到编码问题,所以真正的 Unicode 字符集也不是随意编排字符的。
对⽐以上三种编码⽅案
⾸先,只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。
1) UTF-8 使⽤尽量少的字节来存储⼀个字符,不但能够节省存储空间,⽽且在⽹络传输时也能节省流量,所以很多纯⽂本类型的⽂件(例如各种编程语⾔的源⽂件、各种⽇志⽂件和配置⽂件等)以及绝⼤多数的⽹页(例如百度、新浪、163等)都采⽤ UTF-8 编码。
UTF-8 的缺点是效率低,不但在存储和读取时都要经过转换,⽽且在处理字符串时也⾮常⿇烦。
例如,要在⼀个 UTF-8 编码的字符串中找到第 10 个字符,就得从头开始⼀个⼀个地检索字符,这是⼀个很耗时的过程,因为 UTF-8 编码的字符串中每个字符占⽤的字节数不⼀样,如果不从头遍历每个字符,就不知道第 10 个字符位于第⼏个字节处,就⽆法定位。
不过,随着算法的逐年精进,UTF-8 字符串的定位效率也越来越⾼了,往往不再是槽点了。
2) UTF-32 是“以空间换效率”,正好弥补了 UTF-8 的缺点,UTF-32 的优势就是效率⾼:UTF-32 在存储和读取字符时不需要任何转换,在处理字符串时也能最快速地定位字符。
例如,在⼀个 UTF-32 编码的字符串中查找第 10 个字符,很容易计算出它位于第 37 个字节处,直接获取就⾏,不⽤再逐个遍历字符了,没有⽐这更快的定位字符的⽅法了。
但是,UTF-32 的缺点也很明显,就是太占⽤存储空间了,在⽹络传输时也会消耗很多流量。
我们平常使⽤的字符编码值⼀般都⽐较⼩,⽤⼀两个字节存储⾜以,⽤四个字节简直是暴殄天物,甚⾄说是不能容忍的,所以 UTF-32 在应⽤上不如 UTF-8 和 UTF-16 ⼴泛。
3) UTF-16 可以看做是 UTF-8 和 UTF-32 的折中⽅案,它平衡了存储空间和处理效率的⽭盾。
对于常⽤的字符,⽤两个字节存储⾜以,这个时候 UTF-16 是不需要转换的,直接存储字符的编码值即可。
Windows 内核、.NET Framework、Cocoa、Java String 内部采⽤的都是 UTF-16 编码。
UTF-16 是幕后的功⾂,我们在编辑源代码和⽂档时都是站在前台,所以⼀般感受不到,其实很多⽂本在后台处理时都已经转换成了 UTF-16 编码。
不过,UNIX 家族的操作系统(Linux、Mac OS、iOS 等)内核都采⽤ UTF-8 编码,我们就不去争论谁好谁坏了。
宽字符和窄字符(多字节字符)
有的编码⽅式采⽤ 1~n 个字节存储,是变长的,例如 UTF-8、GB2312、GBK 等;如果⼀个字符使⽤了这种编码⽅式,我们就将它称为多字节字符,或者窄字符。
有的编码⽅式是固定长度的,不管字符编号⼤⼩,始终采⽤ n 个字节存储,例如 UTF-32、UTF-16 等;如果⼀个字符使⽤了这种编码⽅式,我们就将它称为宽字符。
Unicode 字符集可以使⽤窄字符的⽅式存储,也可以使⽤宽字符的⽅式存储;GB2312、GBK、Shift-JIS 等国家编码⼀般都使⽤窄字符的⽅式存储;ASCII 只有⼀个字节,⽆所谓窄字符和宽字符。