Not Only Algorithm,不仅仅是算法,关注数学、算法、数据结构、程序员笔试面试以及一切涉及计算机编程之美的内容 。。
你的位置:NoAlGo博客 » 基础理论 » , , ,

字符编码详解

字符编码涉及到很多概念如ASCII、Unicode、GBK、GB2312、UTF-8等,是在编程过程中经常遇到的问题,同时也是困扰很多新手程序员的难题,正如随处可见的乱码问题。但字符编码又是计算机的重要基础之一,正确处理字符的输入输出、存储显示问题,是进行下一步软件开发或网站建设的必要前提。
本文将从字符集和字符编码等基础概念出发,详细介绍字符编码所涉及到的点点滴滴,力求以后再遇到相关的问题可以清楚了解问题的本质原因并能够由此找到解决问题的办法。

基本概念

计算机中储存的信息都是二进制的0/1串,当我们要在计算机中存储诸如英文、中文、标点符号等字符时,需要先把字符转化成二进制的0/1串之后再保存到计算机中;而当我们要读取保存在计算机中的信息时,需要通过一定的方法把里面的二进制0/1串转化成为原先的字符,然后再通过显示器等渠道进行显示。

这里涉及到字符编码的两个基本概念:

  • 字符集(Character Set):指系统支持的所有字符的集合。字符包括涉及到的所有文字和符号,如各个国家的语言文字、标点符号和图形符号等。
  • 字符编码(Character Encoding):指把字符集中的字符映射成二进制0/1串的规则,如用多少个字节存储,每个字节存什么信息等。例如,可以把字符A映射成二进制的01000001(即十进制的65,十六进制的0×41)。通过编码之后的字符适合于硬盘存储、网络传输等,但若要再次显示时则需要以相反的规则把0/1串映射回原先的字符,称为解码过程。

另外,相关组织在制定编码标准的时候,“字符的集合”和“编码方式”有时是同时制定的,比如我们平常所说的“字符集”如ASCII,GBK,GB2312等,除了有“字符集”这层含义外,同时也包含了“字符编码”的含义。但一些编码标准在规定了一个字符的编码之后,并没有规定这个编码在计算机中是如何存储的,这就涉及到具体的编码方式的问题。比如,Unicode规定了“我”这个汉字编码为0×6211(二进制110001000010001),但它并不限制这个数字具体如何存储,实际情况可能为0xE68891(UTF-8编码),也可能为0xFFFE11(UTF-16编码小端),视具体的编码方案而定。这里不同编码的含义及编码的方法后面见后面具体介绍。

ASCII及其扩展

计算机内部保存信息的二进制0/1串中,每个位置(bit)只有0和1两种状态,一个字节(byte,含有8个二进制位)可以组合出2^8=256种状态,从0000000到11111111。如果每种状态对应一个字符,那么一个字节可以表示256个字符。 ASCII码就是规定了这256个中的一部分字符的编码标准。ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是美国在1967年制定的一套字符编码,规定了英语字符及其他常见字符与二进制位之间的映射关系,是沿用至今的影响深远的编码系统。

ASCII码使用8位二进制数组合来表示256种可能的字符。标准ASCII码(又称基础ASCII码)使用7位二进制数来表示所有的大写和小写字母、数字0到9、标点符号以及在美式英语中使用的特殊控制字符,一共128个。其中:

  • 0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。如
    • 控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;
    • 通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;
  • 32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

标准ASCII编码只使用了一个字节(占8个二进制位)中的7位,其最高位可以统一填充0,也可以用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位添1。

ASCII主要用于显示现代英语,128个符号对于英语来说已经足够,但对于其他语言,由于他们的字符数量众多,128个符号显得远远不够。一些国家通过利用字节中闲置的最高位编入新的特殊符号、外来语字母和图形符号,可以勉强显示一些西欧国家的语言,称为扩展ASCII码。在扩展ASCII码中,0-127这前128个编码表示的符号是一样的,但128–255这后128个则因不同的国家而各不相同。
具体的ASCII表可以参考维基百科ASCII

中文

ASCII编码能够很好地对英语字符进行编码,但对大量的中文汉字则无能为力,为了显示中文,相关组织设计了相应的中文编码标准。

GB 2312

GB 2312(GB 2312-80,信息交换用汉字编码字符集·基本集,又称GB0) 是中华人民共和国国家标准简体中文字符集,由中国国家标准总局发布,1981年5月1日实施。GB2312编码通行于中国大陆,新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。

  • 01-09区为特殊符号。
  • 16-55区为一级汉字,按拼音排序。
  • 56-87区为二级汉字,按部首/笔画排序。
  • 10-15区及88-94区则未有编码。

在使用GB2312的程序通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。

举例来说,为了求汉字“我”的GB2312编码,首先查找区位码表,结果内容节选如下:

挝(4646) 蜗(4647) 涡(4648) 窝(4649) 我(4650)

则可知“我”位于第46区的第50位,则高位字节为:46+0xA0=0xCE,低位字节为:50+0xA0=0xD2,则啊的GB2312编码为0xCED2。
通过查找GB2312编码表,可以验证上述上述结果的正确性。

简单地说,GB2312中的ASCII字符占一个字节,且最高位为0,而中文占两个字节,且两个字节的最高位均为1(表示其不是普通的ASCII字符),这两个字节去除最高位的1后分别产生该字符的区码和位码,然后通过区位码表可以得到其对应的字符。

GBK

GBK编码,全称为《汉字内码扩展规范》,GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称为Chinese Internal Code Specification,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。
简单地说,GBK就是把B2312没有用到的码位找出来用上,并且不再要求低字节的最高位一定是1,只要第一个字节的最高位是1就表示这个字节和下一个字节共同表示一个汉字,而不管下一个字节的最高位是否为1。于是,GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

GB 18030

GB 18030,全称为国家标准GB 18030-2005《信息技术中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB 18030-2000《信息技术信息交换用汉字编码字符集基本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点:

  1. 采用单字节、双字节和四字节三种方式对字符编码。
  2. 编码空间庞大,最多可定义161万个字符。
  3. 支持中国国内少数民族的文字,不需要动用造字区。
  4. 汉字收录范围包含繁体汉字以及日韩汉字。

BIG5

Big5(大五码,五大码),是繁体中文社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。Big5虽普及于台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。倚天中文系统、Windows繁体中文版等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003年,Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。

Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。“高位字节”使用了0×81-0xFE,“低位字节”使用了0×40-0x7E,及0xA1-0xFE。在Big5的分区中:

0×8140-0xA0FE 保留给使用者自定义字符(造字区)。
0xA140-0xA3BF 标点符号、希腊字母及特殊符号。
0xA3C0-0xA3FE 保留。此区没有开放作造字区用。
0xA440-0xC67E 常用汉字,先按笔划再按部首排序。
0xC6A1-0xC8FE 保留给使用者自定义字符(造字区)。
0xC940-0xF9D5 次常用汉字,亦是先按笔划再按部首排序。
0xF9D6-0xFEFE 保留给使用者自定义字符(造字区)。

ANSI

不同的国家和地区制定了不同的标准,由此产生了 GB2312、BIG5、JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 当然对于ANSI编码而言,0×00~0x7F之间的字符,依旧是1个字节代表1个字符。这一点是ASNI编码与Unicode编码之间最大也最明显的区别。

Windows的记事本有个BUG,在新建文本文档中只输入“联通”2字保存再打开时将是乱码。
当txt文档中一切字符都在 C0≤第一个字节≤DF 并且 80≤第二个字节≤BF 这个范围时,notepad都无法确认文档的格式,没有自动依照UTF-8格式来显示。 而"联通"就是C1 AA CD A8,刚好在上面的范围内,所以不能正常显现。
记事本默认是以ANSI编码保存文本文档的,而正是这种编码存在的bug招致了上述怪现象。假如保存时选择Unicode、Unicode(big endian)、UTF-8编码就正常了。此外,假如以ANSI编码保存含有某些特别符号的文本文档,再次打开后符号也会变成英文问号。

Unicode

世界上各个国家都有着其独有的编码方式,当不同国家的资料通过互联网进行就容易出现乱码问题,只有知道一个文件原始的编码方式才能正确地进行解码显示。为了解决这个问题,促进使国际间的信息交流,Unicode字符集应运而生。Unicode(统一码、万国码、单一码、标准万国码)是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案,它为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台的文本转换及处理要求。可以想象,Unicode的字符集非常庞大,并且现在仍然在继续增长。

Unicode是一个很大的集合,现在的规模可以容纳100多万个符号。Unicode用“U+”加一组十六进制数字来表示一个字符。Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符。 Unicode的编码空间可以划分为17个平面(plane),每个平面包含2^16=65,536个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从00到10,共计17个平面。第一个平面称为基本多文种平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。

Unicode只规定了每个字符映射到的数字,但其不限制这个数字具体在计算机中怎么存储,这个工作由具体的字符编码方案进行。Unicode的编码方案包括UTF-32,UIF-16,UTF-8,以下一一进行介绍。,但在介绍编码方案前先简单介绍相关的概念。

字节序

因为Unicode是多字节编码,存储数据时有字节序的问题。字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
常见的字节序主要有以下两种:

  • 小端字节序(LE,Little Endian):将低序字节存储在低地址
  • 大端字节序(BE,Big Endian):将高序字节存储在低地址

例如,对于数据0×1234,对于LE,存储为34 12,因为34是原始数据的低位,保存在地址较低的开始位置,12是原始数据的高位,保存在地址较高的结尾位置。而对于BE,则存储为12 34,原因类似可得。
可以看到,大端字节序的表示方法与我们的阅读习惯一致,而小端字节序则相反。另外,大端字节序也被采用为网络字节序。

BOM

BOM(byte-order mark,字节顺序标记)是用来标示编码字节序的标志,常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的记号。

字符U+FEFF出现在字节流的开头,用来标识该字节流的字节序,是高位在前还是低位在前。
在UTF-16中,字节顺序标记被放置为文件或字符串流的第一个字符,以标示在此文件或字符串流中,以所有十六比特为单位的字码的尾序(字节顺序)。
如果十六比特单位被表示成大端字节序,这字节顺序标记字符在串行中将呈现0xFE,其后跟着0xFF。如果十六比特单位使用小端字节序,这个字节串行为0xFF,其后接着0xFE。
在Unicode中,值为U+FFFE的码位被保证将不会被指定成一个Unicode字符。这意味着0xFF、0xFE将只能被解释成小尾序中的U+FEFF(因为不可能是大尾序中的U+FFFE)。

UTF-8则没有字节顺序的议题。UTF-8编码过的字节顺序标记则被用来标示它是UTF-8的文件。它只用来标示一个UTF-8的文件,而不用来说明字节顺序。许多Windows程序(包含记事本)会添加字节顺序标记到UTF-8文件。然而,类Unix系统则不被建议采用这种作法。因为它会妨碍到如解译器脚本开头的Shebang等的一些重要的码的正确处理。它亦会影响到无法识别它的编程语言。如gcc会报告源码文件开头有无法识别的字符。而在PHP中,如果没有激活输出缓冲(output buffering),它会使得页面内容开始被送往浏览器(即:用户头文件已被提交),这使PHP脚本无法指定用户头文件(HTTP Header)。字节顺序标记在UTF-8中被表示为串行EF BB BF,对大部分未准备好处理UTF-8的文本编辑器及网页浏览器而言,在ISO-8859-1的环境中则会显示???。

虽然字节顺序标记亦可以用于UTF-32,但这个编码很少用于传输,其规则如同UTF-16。对于已于IANA注册的字符集UTF-16BE、UTF-16LE、UTF-32BE和UTF-32LE等来说,不可使用字节顺序标记。文文件开头的U+FEFF会被解释成一个(已舍弃的)"零宽度无断空白",因为这些字符集的名字已决定了其字节顺序。对于已注册字符集UTF-16和UTF-32来说,一个开头的U+FEFF则用来表示字节顺序。

不同编码的字节顺序标记的表示

编码 表示(十六进制) 表示(十进制)
UTF-8 EF BB BF 239 187 191
UTF-16(大端序) FE FF 254 255
UTF-16(小端序) FF FE 255 254
UTF-32(大端序) 00 00 FE FF 0 0 254 255
UTF-32(小端序) FF FE 00 00 255 254 0 0
GB-18030 84 31 95 33 132 49 149 51

UTF-32

UTF-32(32-bit Unicode Transformation Format)编码方案使用4个字节来表示所有的字符,每个数字代表唯一的至少在某种语言中使用的字符。因为每个字符都是使用4字节存储,UTF-32的空间使用效率比较低,但好处是其可以在常数时间内定位字符串里的任意字符(第n个字符的下标为4*n)。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。

UTF-16

UTF-16(16-bit Unicode Transformation Format)编码方案使用2个字节来表示字符。事实上2个字节最多只能表示2^16 = 65536 = 0×10000个字符,而实际上的全世界的字符数量远大于这个数值,UTF-16需要用特殊的方法来达到它的目的。

由前面的介绍可知,在Unicode中位于0×00000~0×10000之间的码位属于BMP(基本多文种平面)内,在BMP内的字符直接用2个字节进行表示。注意,在BMP内位于U+D800~U+DFFF之间的码位区段是永久保留不映射到Unicode字符的,而UTF-16就利用这个保留下来的区段对辅助平面的字符进行编码。
在Unicode中为0×00000~0×10000之外的码位属于辅助平面,不能直接用2个字节表示,UTF-16实际上使用了2个2字节的码元表示,因此对于这部分字符,UTF-16实际上是使用了4字节进行表示的,但由于最常用的的字符基本上都位于BMP中,所以出现在这里的字符数量是比较少的。

对于一个位于辅助平面的码元U,由以下步骤得到它在UTF-16的编码:

  1. 令U’=U-0×10000,则U’的范围为0~0xFFFFF,长度为20比特。
  2. 令U1=U’高位的10比特的值(范围为0~0x3FF)+0xD800,则U1的范围为0xD800~0xDBFF,称为前导代理。(加法操作可改为逻辑或操作,效果一样)
  3. 令U2=U’低位的10比特的值(范围为0~0x3FF)+0xDC00,则U1的范围为0xDC00~0xDFFF,称为后尾代理。(加法操作可改为逻辑或操作,效果一样)
  4. U1和U2,一共4个字节,构成了原先对应于U的字符在UTF-16下的编码。

上述算法可理解为:辅助平面中的码位从U+10000到U+10FFFF,共计FFFFF个(2^20=1,048,576个),需要20位的空间来表示。如果用两个16位长的整数组成的序列来表示,第一个整数(前导代理)要容纳上述20位空间的前10位,第二个整数(后尾代理)容纳容纳上述20位空间的后10位。还要能根据16位整数的值直接判明属于前导整数代理的值的范围(2^10=1024),还是后尾整数代理的值的范围(也是2^10=1024)。因此,需要在基本多语言平面中保留不对应于Unicode字符的1024+1024=2048个码位,就足以容纳前导代理与后尾代理所需要的编码空间。这对于基本多语言平面总计65536个码位来说,仅占3.125%.
由于前导代理、后尾代理、BMP中的有效字符的码位,三者互不重叠,一个字符编码的一部分不可能与另一个字符编码的不同部分相重叠。这意味着UTF-16是自同步(self-synchronizing),可以通过仅检查一个码元就可以判定给定字符的下一个字符的起始码元。

下面以一个具体例子说明UTF-16的编码过程。

// 假设某个Unicode字符映射到了V,但V大于65536,不能用2字节表示。
// 于是需要使用4字节进行表示。
V = 0x64321

// 计算V与0x10000的差值。
Vx = V - 0x10000 = 0x54321 = 0101 0100 0011 0010 0001

// 得到差值的高10位和低10位。
Vh = 01 0101 0000 // Vx的高位部份的10 bits
Vl = 11 0010 0001 // Vx的低位部份的10 bits
w1 = 0xD800 //結果的前16位元初始值
w2 = 0xDC00 //結果的後16位元初始值

// 计算前导代理和后尾代理
w1 = w1 | Vh = 1101 1000 0000 0000 | 01 0101 0000 = 1101 1001 0101 0000 = 0xD950
w2 = w2 | Vl = 1101 1100 0000 0000 | 11 0010 0001 = 1101 1111 0010 0001 = 0xDF21

// 最终这个字符最后正确的UTF-16编码应该是:
0xD950 DF21 //大端字节序
0x50D9 21DF //小端字节序

UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字的应用中,优先采用的编码。

UTF-8使用一至四个字节为每个字符编码:
1. 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
2. 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U+0080至U+07FF)。
3. 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码(Unicode范围由U+0800至U+FFFF)。
4. 其他极少使用的Unicode 辅助平面的字符使用四字节编码。

Unicode和UTF-8之间的转换关系表如下:

Unicode范围 UTF-8二进制位含义
U+00000000–U+0000007F 0xxxxxxx
U+00000080–U+000007FF 110xxxxx 10xxxxxx
U+00000800–U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+00010000–U+0010FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

以上表格中的x表示可以用来填入实际的数据。其实上表显示的编码规则很简单:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。即对于英语字母,UTF-8编码和ASCII码相同。
  2. 对于多字节的符号(设字节数为n),第一个字节的前n位都为1,第n+1位为0,后面紧接着的n个字节的前两位均为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

UTF-8编码具有以下优点:

  1. ASCII是UTF-8的一个子集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
  2. 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
  3. UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。
  4. 任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
  5. UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。

UTF-8编码具有以下缺点:

  1. 与其他Unicode编码相比,特别是UTF-16,在UTF-8中ASCII字符占用的空间只有一半,可是在一些字符的UTF-8编码占用的空间就要多出1/3,特别是中文、日文和韩文(CJK)这样的方块文字。
  2. 变长编码每个字符使用了不同数量的字节,所以定位字符串里的第N个字符时需要从头开始逐个遍历,复杂度为O(N)。

下面以具体的例子讲解UTF-8的编码A方法。
以汉字“我”为例,编码时需要先查Unicode编码表,下面是部分内容节选:

U+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
6210

得到汉字“我”所在行为6210,所在列为1,则最终Unicode的码元为:U+6210 + 1 = U+6211。根据Unicode和UTF-8之间的转换关系表可知,0×6212位于第三行的情况,把其二进制(110001000010001)填入第三行的x中,左边不够的补0,最终得到的UTF-8编码为二进制的11100110 10001000 10010001(十六进制的0xE68891)。
使用Notepad++进行验证:新建文件,输入一个汉字“我”,然后选择Encoding->Encode in UTF-8 Without BOM,保存。然后使用Notepad++插件HEX-Editor或者其他二进制查看软件打开,即可看到文件内容只有3个字节:0xE6 88 91。

UCS

UCS(Universal Multiple-Octet Coded Character Set,通用字符集)是由国际标准化组织ISO制定的ISO 10646(ISO/IEC 10646)标准所定义的标准字符集。
历史上存在两个独立的尝试创立单一字符集的组织:ISO组织和Unicode组织,前者开发的ISO/IEC 10646项目,后者开发的Unicode项目,因此最初制定了不同的标准。后来两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。

Unicode在和UCS编码保持一致,在实现上有自己的规则。Unicode具体的编码方式有UTF-8,UTF-16,UTF-32等,而UCS编码的编码方式有UCS-2和UCS-4等。Unicode和UCS的编码方式非常相似,但又略有不同。
UCS-2使用两字节编码,UTF-16也是采用相同的两字节编码,后来发现不够用,于是扩展了UCS-2,利用BMP内永久保留不映射到字符的(U+D800~U+DFFF)空间来对辅助平面的字符的码位进行编码,于是能表示BMP和16个辅助平面的字符。
UCS-4是四字节编码(实际上只用了31位,最高位必须为0),理论上编码范围能达到U+7FFFFFFF,但是因为unicode和iso达成共识,只会用17个平面内的字符,所以UTF-32是UCS-4的子集。但是UTF-32是定长编码,和UCS-4无论实现和编码都是基本一样的。

当前Unicode深入人心,UTF-8大行其道,UCS基本淡出人们的视野,UCS编码基本被等同于UTF-16,UTF-32(在Notepad++编码选项中可以设置UCS-2大小端模式)。

记事本编码

用记事本另存为时,可以选择保存文本使用的的几种编码模式,分别为:

  • ANSI:默认保存的编码格式,采用本地操作系统默认的内码,简体中文一般为GB2312。
  • Unicode:UTF-16的小端字节序,加上BOM签名:0xFFFE。
  • Unicode bigendian:Unicode编码:UTF-16的大端字节序,加上BOM签名:0xFEFF。
  • UTF-8:编码格式是:UTF-8,其BOM为0xEF BB BF(UTF-8不区分字节序,这个BOM仅标志UTF-8编码)。

本文在具体的概念上参考了百度百科、维基百科多个条目以及阮一峰的字符编码笔记,感兴趣的读者可以进一步前往探究。

上一篇: 下一篇:
  1. C0≤第一个字节≤DF 并且 80≤第二个字节≤BF 这句话能详解一下么

    • 意思是编码之后的第一个字节的数值位于十六进制的的C0和DF之间,第二个字节数值位于80和BF之间。

我的博客

NoAlGo头像编程这件小事牵扯到太多的知识,很容易知其然而不知其所以然,但真正了不起的程序员对自己程序的每一个字节都了如指掌,要立足基础理论,努力提升自我的专业修养。

站内搜索

最新评论