专栏首页C/C++基础精述字符编码

精述字符编码

带你了解ASCII,Latin1,ANSI,Unicode,UCS-2,UCS-4,UTF-8,UTF-16,UTF-32,GB2312,GB13000,GBK,GB18030,BIG5,BMP,Code Page,BOM,MBCS,Little Endian,Big Endian,内码,外码。

1.导语

字符编码(Character Encoding)是计算机显示文本的基础,是每一位IT从业者必知的计算机基础知识点,如同数值在计算中如何存储表示,那么基础,那么重要。然因字符编码历史久远,变更频繁,地域差别,参考文献内容不全,质量参差不齐等原因,让不少读者望而却步,坚持刨根究底的读者,最终也难免云里雾里,不知所以然。鉴于此,本文将尝试带领大家弄清楚字符编码相关术语的概念,各自间的联系和区别,不足之处,请读者批评指正,不甚感激。

关于字符编码的介绍,网上已经有很多前人留下了值得参考的文章,这里推荐几篇,建议在阅读本篇博文前,请大家研读以下几篇文章,阅读顺序不作要求。 (1)字符编码笔记:ASCII,Unicode和UTF-8 (2)各种编码UNICODE、UTF-8、ANSI、ASCII、GB2312、GBK详解 (3)闲谈字符和字符集以及编码(上) (4)闲谈字符和字符集以及编码(下) (5)字节那些事儿 (6)彻底搞懂字符编码(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian…)

2.ASCII

我们知道计算机存储数据都是以二进制形式存储的,以字节(Byte)为最小存储单位,以比特(Bit)为最小状态。每一个Bit取值为0或1两种状态,每一个字节有8个Bit位,也就是一个字节可以表示256种状态。那计算机是如何存储和识别0和1这两种状态的呢?计算机中0和1分别由低电平(低电压)和高电平(高电压)表示,实现的硬件基础就是晶体二极管,原理就是利用了晶体二极管的单向导电性。

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。上个世纪60年代,由美国制定的一套字符编码,将英语字符与二进制位之间做了统一规定。主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统。它已被国际标准化组织(ISO)定为国际标准,称为ISO/IEC 646

ASCII编码一共规定了128个字符的编码,比如空格(Space)码值是32(二进制00100000),大写字母A码值是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

3. Latin1

Latin1是国际标准编码ISO-8859-1的别名。Latin1也是单字节编码,在ASCII编码的基础上,利用了ASCII未利用的最高位,扩充了128个字符,因此Latin1可以表示256个字符,并向下兼容ASCII。Latin1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中,在后来的修订版ISO-8859-15加入了欧元符号。Latin1的编码范围是0x00-0xFF,ASCII的编码范围是0x00-0x7F。

Latin1相对ASCII而言,较少被提及,其实Latin1的使用还是比较广泛的,比如MySQL的数据表存储默认编码就是Latin1。

4.ANSI

说到ANSI,大家也许会认为是美国国家标准委员会(American National Standards Institute),但是本文讨论的是字符编码,此处的ANSI是指字符编码。

每个国家和地区为了表示自己的文字字符,各自制定了不同的编码标准,由此产生了GB2312、GBK、GB18030、Big5、Shift_JIS等不同的编码。ANSI编码不是单一明确的字符编码,是对不同国家和地区不同编码的一个统称,根据当前系统的语言环境采用相应的编码方式。比如,Windows环境下通过代码页(Code Page)来区分具体编码,将代码页设置为936,那么ANSI代表GBK(简体中文);代码页设置为950,ANSI代表Big5(繁体中文);代码页设置为932,ANSI 代表 Shift_JIS(日文)。可见,代码页是具体字符编码的代号。

ANSI编码最常见的应用就是在Windows当中的记事本程序中,当新建一个记事本,默认的保存编码格式就是ANSI。不同 ANSI编码之间互不兼容,当信息在国际间交流时,就时常会出现令人头痛的乱码问题。要想查看Windows系统使用的代码页,在命令行输入chcp命令后回车查看。

5.GB2312,GB13000,GBK,GB18030,BIG5

本节讨论的内容主要围绕中文编码的发展以及各自编码之间的关系。计算机史上,中国大陆以及中国台湾、中国香港等地区自行研发的中文编码方案主要有GB2312,GBK,GB18030,BIG5,下面将一一讲解其大致的发展和特点。

GB2312。 GB2312(又名GB2312–1980)是中华人民共和国国家标准简体中文字符集,全称《信息交换用汉字编码字符集》,由中国国家标准总局1980年发布,1981年5月1日实施。GB2312通行于中国大陆,新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。

GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个。同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。但对于人名、古汉语等方面出现的罕用字和繁体字,GB2312不能处理,因此后来GBK以及GB18030汉字字符集的相继出现解决了这些问题。

GB2312中,如果一个字节是0~127,那么这个字节的含义同ASCII编码,否则,这个字节和下一个字节共同组成汉字(或是GB编码定义的其他字符)。所以GB2312对ASCII编码是兼容的。也就是说,如果一段用GB2312编码的文本里所有字符都在ASCII中有定义,那么这段编码和ASCII编码完全一样。

GB13000、GBK及GB18030的由来。 GB编码早期收录的汉字不足一万个,基本满足日常使用需求,但不包含一些生僻的字,后来在一个个新版本中加进去。最早的GB编码是GB2312,由于GB2312-80只收录6763个汉字,根本不够用。1993年,随着Unicode 1.1版本推出,收录中国大陆、中国台湾、日本及韩国通用字符集的汉字,总共有20,902个。同年,我国按捺不住,制定了等同于Unicode 1.1版本的国家中文编码标准GB13000(全称:GB13000.1-93),采用双字节编码,但因其与GB2312不兼容,没有照顾到市场上软件厂商的感情,因为大部分中文软件都是采用了GB2312,所以一时间,GB13000并没有得到广泛的应用,现如今已是废弃的标准,这也是我们很少听到这个编码标准的原因。

GB13000虽然没有得到应用,但是收录了很多GB2312没有收录的汉字,还是起到了一定的作用。如部分在GB2312-80推出以后才简化的汉字(如“啰”),部分人名用字(如中国前总理朱镕基的“镕”字),中国台湾及中国香港使用的繁体字,日语及朝鲜语汉字等。参考GB13000收录的汉字,微软利用GB2312未使用的编码空间,与中国合作制订了GBK。GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的首字母,英文名称:Chinese Internal Code Specification)。因为微软的介入,GBK只被中华人民共和国国家有关部门作为技术规范,并非国家正式标准,只是国家技术监督局标准化司、电子工业部科技与质量监督司发布的“技术规范指导性文件”。虽然 GBK收录了所有Unicode 1.1及GB13000之中的汉字,但是编码方式与Unicode 1.1及GB13000不同。仅仅是GB2312到GB13000之间的过渡方案。但因为其在Windows95简体中文版开始使用,得到了广泛的推广,成为了事实上不争的中文编码标准。GBK是在GB2312标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从0x8140至0xFEFE,排除部分码位,共23940个码位,共收录了21003个汉字和883个图形符号,完全兼容GB2312,支持国际标准ISO/IEC10646-1和国家标准GB13000收录的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年10月制定, 1995年12月正式发布,目前中文版操作系统Win95、Win98、Windows NT以及Windows 2000、Windows XP、Win7、Win8、Win10等都支持GBK编码方案。

最新的中文编码是GB18030,国家质量技术监督局于2000年3月17日推出了GB18030-2000标准,以取代GBK,加入了一些国内少数民族的文字,一些生僻字被编到4个字节,每扩展一次都完全保留之前版本的编码,所以每个新版本都向下兼容。

Big5的由来。 20世纪80年代初期,中国大陆制订了GB2312,可能因为改革开放搞得热火朝天,无暇顾及,没有考虑到中国台湾,中国香港,中国澳门同胞的使用繁体中文的情况,没有帮他们制定繁体中文编码(主要原因是还没回归),但是台湾同胞怎么能够容忍这种情况,于是自己搞了个Big5繁体中文编码。Big5又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。Big5虽普及于港澳台地区等繁体中文通行区,但长期以来并非当地的国家/地区标准或官方标准,而只是业界标准。倚天中文系统、Windows繁体中文版等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003年,Big5被收录到CNS11643中文标准交换码的附录当中,获取了较正式的地位。这个最新版本被称为Big5-2003。

GBK、GB18030、Big5与ANSI的关系。 前面对ANSI已经有较详细的介绍,讲到了GBK,GB18030,Big5,这里再啰嗦一遍。

除了中文本土的编码方案,同样,日文、韩文、世界各国文字都有它们各自的编码(如果ASCII不能满足使用要求的话)。这些编码都和GB编码相似,兼容ASCII并用两个字节表示一个字。所有这些各国文字编码,微软统称为ANSI 。所以即使知道是ANSI,我们还需要知道这是哪国文字才能解码,因为这些编码都互相冲突。另外,你无法用一段ANSI编码表示既有汉字、又有韩字的文本。ANSI根据代码页来确定代表的具体编码,例如简体中文GB2312的代码页是936。

小结。 说了那么多,简单的梳理一下中文相关编码之间的关系。从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。其中,GBK包含了BIG5编码中的所有汉字,但是GBK不兼容BIG5。GB13000码值与Unicode中文字符码值相同,与其它中文GB编码均不兼容。它们的关系如下图所示:

6.Unicode与BMP

我们知道英语用128个符号编码ASCII就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用编码Latin1,就可以表示最多256个符号。

但是对于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。单字节编码方案最多只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。这里不详细展开,后面会具体讨论GB2312。每个国家或地区都有自己的一套编码方案,于是当信息在国际间间流是就会出现乱码问题,好比世界上每个国家都有自己的语言,相互交流时就会出现障碍。于是,就需要一个国际语言,让每个国家和地区的人之间可以正常的交流,对于计算机也是同样的道理,需要一个统一的字符编码方案,让每一台电脑都能正确的识别字符。铺垫了那么多,就是想说明一个叫Unicode的字符编码横空出世的必要性和意义。

Unicode俗称万国码,是由统一码联盟在1991年首次发布,请注意,并非由ISO发布。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以跨语言环境来呈现和处理文字。需要注意的是,Unicode虽然称为万国码,但是目前也不能涵盖世界上所有的文字字符,因为Unicode自发布以来,至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2016年6月21日公布的Unicode9.0.0,已经收入超过十万个字符(我们华夏民族的字符也不止十万个啊,Uinicode仍需努力啊)。

Unicode的编码方式。 Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符. Unicode的编码空间可以划分为17个平面(plane),每个平面包含2162162^{16}(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016001600_{16}到1016101610_{16},共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区块是永久保留不映射到Unicode字符。实际使用中,目前只用了少数平面内编码的字符。

平面

始末字符值

中文名称

英文名称

0号平面

U+0000 - U+FFFF

基本多文种平面

Basic Multilingual Plane,简称BMP

1号平面

U+10000 - U+1FFFF

多文种补充平面

Supplementary Multilingual Plane,简称SMP

2号平面

U+20000 - U+2FFFF

表意文字补充平面

Supplementary Ideographic Plane,简称SIP

3号平面

U+30000 - U+3FFFF

表意文字第三平面(未正式使用[1])

Tertiary Ideographic Plane,简称TIP

4号平面~13号平面

U+40000 - U+DFFFF

(尚未使用)

14号平面

U+E0000 - U+EFFFF

特别用途补充平面

Supplementary Special-purpose Plane,简称SSP

15号平面

U+F0000 - U+FFFFF

保留作为私人使用区(A区)

Private Use Area-A,简称PUA-A

16号平面

U+100000 - U+10FFFF

保留作为私人使用区(B区)

Private Use Area-B,简称PUA-B

其中,中国由GB2312编码表示的常用的6763个汉字就被收录在Unicode的0号平面内U+4E00-U+9FFF码值之间,该区间的码值也包含也很多非常用的中文汉字,共收录了2W多个汉字。Unicode对各国语言文字的编码情况具体可参见维基百科Unicode字符平面映射

7.UCS、UCS-2与UCS-4

说到字符编码,大家肯定听过UCS-2和UCS-4,在说完Unicode,好学的大家肯定心存疑惑,UCS-2和UCS-4和Unicode之间的关系和区别到底是什么?我曾经也为此痛苦不已,但是下面我将努力尝试捋清楚UCS-2与Unicode之间千丝万缕的关系,为大家答疑解惑。

首先说一下什么是UCS。 UCS(Universal Character Set,通用字符集)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS又被称为Universal Multiple-Octet Coded Character Set,中国大陆译为通用多八位编码字符集,中国台湾译为广用多八比特编码字元集。

说到UCS,不得不说UCS和Unicode的关系。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)于1984年创建的ISO/IEC JTC1/SC2/WG2(英文全称:International Organization for Standardization / International Electrotechnical Commission, Joint Technical Committee#1/Subcommittee#2/Working Group#2)和由Xerox、Apple等软件制造商于1988年组成的统一码联盟。前者开发了ISO/IEC 10646(UCS)项目,后者开发了统一码(Unicode)项目。因此最初制定了不同的标准。1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。1991年,不包含CJK统一汉字集的Unicode 1.0发布。随后,CJK统一汉字集的制定于1993年完成,发布了ISO 10646-1:1993,即Unicode 1.1。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码。ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都独立存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。也就是说,我们可以简单的理解Unicode和UCS是两个不同机构发布的对全球文字字符进行统一编码的相同方案,更为简单粗暴的理解就是“Unicode=UCS”。

UCS与Unicode的区别。 UCS和Unicode毕竟是两个不同机构研发的编码方案,它们之间还是存在着一些区别。Unicode和UCS虽然对全球字符编码的码值相同,ISO/IEC 10646标准,就像ISO/IEC 8859标准一样,只不过是一个简单的字符集表,但Unicode标准,额外定义了许多与字符有关的语义符号学。Unicode详细说明了绘制某些语言(如阿拉伯语)表达形式的算法,处理双向文字(比如拉丁文和希伯来文的混合文字)的算法,排序与字符串比较所需的算法,等等。此外两者部分样例字形有显著的区别。ISO/IEC 10646-1标准同样使用四种不同的风格变体来显示表意文字如中文、日文、韩文(即CJK),但Unicode 2.0的表里只有中文的变体。甚至存在“Unicode对日本用户来说不可接受”的不实传说。

UCS是ISO研发的全球通用字符集,那么UCS-2又是什么呢? UCS-2(2-byte Universal Character Set,两字节通用字符集)是一个实际使用的字符编码方案,是UTF-16的前身。还记得前面说到的Unicode的BMP吗,就是Unicode使用两字节来编码全球大部分文字字符的一个编码区间,号称0号平面,UCS-2是一个固定两字节长度的编码,每一个字符都采用一个单一的16位值来表示,因此只能表示Unicode的BMP范围的码值从U+0000到U+FFFF的字符。那么UCS-2和Unicode的0好平面又是啥关系呢?其实UCS-2编码的字符和Unicode的BMP编码的字符是相同的,因此UCS-2就是Unicode的BMP。那么UCS-2是那个机构颁发的呢,很显然是ISO。那么UCS-2和UCS有时什么关系呢?UCS-2是UCS的子集,UCS-2是UCS的编码方式之一。其中,中文范围 4E00-9FBF,即CJK 统一表意符号 (CJK Unified Ideographs)。

UCS-4又是什么呢? UCS-2采用两个字节编码字符,只能标识65536个字符,对于Unicode编码的字符已经超过了十万个,很显然UCS-2只能标识了Unicode的0号平面字符,对于其它辅助平面字符,UCS-2就无能为力,心有余而力不足了。于是ISO 10646标准定义了一个4字节31位的编码形式,称作UCS-4,来标识的Unicode其它辅助平面编码的字符。UCS-4对所有的字符均采用四字节31位编码形式,码值范围是0x00000000-0x7FFFFFFF。

简短总结。 UCS-2=Unicode BMP,Unicode是UCS-4的子集。

8.UTF-8、UTF-16与UTF-32

8.1UTF-8

大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。上面关于Unicode编码系统的解释,主要叙述了其的编码方式,即Uinicode每一个字符赋予了确切的不同的码值,但是实际使用当中,其实现方式是不同于编码方式的。一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同,这就是为何已经存在了UCS-2和UCS-4,仍提出UTF-8、UTF-16和UTF-32。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,UTF)。

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

UTF-8的编码方式。 UTF-8就是以8位为单元对UCS进行编码,而UTF-8不使用大尾序(大端字节序)和小尾序(小端字节序)的形式,每个使用UTF-8存储的字符,除了第一个字节外,其余字节的头两个比特都是以”10”开始,使文字处理器能够较快地找出每个字符的开始位置。但为了与以前的ASCII码兼容(ASCII为一个字节),因此UTF-8选择了使用可变长度字节来存储Unicode:

Unicode 和 UTF-8 之间的转换关系表 ( x 字符表示码点占据的位 )。

码点的位数

码点起值

码点终值

字节序列

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

7

U+0000

U+007F

1

0xxxxxxx

11

U+0080

U+07FF

2

110xxxxx

10xxxxxx

16

U+0800

U+FFFF

3

1110xxxx

10xxxxxx

10xxxxxx

21

U+10000

U+1FFFFF

4

11110xxx

10xxxxxx

10xxxxxx

10xxxxxx

26

U+200000

U+3FFFFFF

5

111110xx

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

31

U+4000000

U+7FFFFFFF

6

1111110x

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

必须要注意的是,2003年十一月,UTF-8被RFC 3629限制了长度,因为Unicode码值0x000000-0x10FFFF,只有21位被编码,所以使用4个字节的就可以编码现有的Unicode字符,所以UTF-8被缩减为四字节,码值由原来的1~6字节缩减为1-4字节,新的UTF-8编码方式与Unicode编码的对应关系如下:

字节数

码点位数

码点起值

码点终值

Byte 1

Byte 2

Byte 3

Byte 4

1

7

U+0000

U+007F

0xxxxxxx

2

11

U+0080

U+07FF

110xxxxx

10xxxxxx

3

16

U+0800

U+FFFF

1110xxxx

10xxxxxx

10xxxxxx

4

21

U+10000

U+10FFFF

11110xxx

10xxxxxx

10xxxxxx

10xxxxxx

已知“严”的unicode是4E25(01001110 00100101),根据上表,可以发现4E25处在第三行的范围内(U+0800-U+FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。我们以Notepad++(需要安装插件HEX-Editor,安装方法)查看中文”严”字的UTF-8编码。

8.2UTF-16

UTF-16类似于UTF-8,都是变长字符编码,都是Unicode的实现方式之一。但是与UTF-8的区别主要有UTF-16最短编码长度是2个字节,UTF-8最短是1个字节,还有就是UTF-8不存在字节序的问题,UTF-16存在字节序的问题。与此同时,UTF-16还利用了Unicode保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。这里我想问,有了应用广泛的UTF-8,为何还要搞个UTF-16呢?还记得前面因为UCS-2的双字节码位不够,IEEE提出的UCS-4编码吗,因为UCS-4规定了每一个字符需要4个字节,31bits来表示,这样太浪费存储空间了,为了解决这个问题,于是IETF(The Internet Engineering Task Force,国际互联网工程任务组)于2000年提出了UTF-16的编码方案并发表在RFC 2781

UTF-16编码方式。 UTF16使用1个或2个16位长的码元来表示,是一个变长编码方案,实现方式如下:

码值范围

10进制码值范围

字节数

UTF-16编码

U+0000-U+FFFF

0-65535

2

xxxxxxxxxxxxxxxx

U+10000-U+10FFFF

65536-1114111

4

110110yyyyyyyyyy 110111xxxxxxxxxx

对于码值从U+0000至U+FFFF的零号平面字符,UTF-16编码的码值与Unicode码值相同。对于码值从U+10000至U+10FFFF的辅助平面字符,UTF-16编码方式将Unicode码值减去0x10000,将码值范围变成0x00000-0FFFFF,填入上面表格第二行的20 bits中。从左至右,两个码元的取值范围分别是0xD800-0xDBFF和0xDCFF-0xDFFFF。由于码值0xD800-0xDFFFF在Unicode零号平面内不表示任何字符,故在UTF-16中用作代理,来表示码值从U+10000至U+10FFFF的辅助平面字符。

UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2字节)存储,但UTF-16却无法兼容于ASCII编码,因UTF-8兼容 ASCII,能适应许多 C 库中的 ‘\0’结尾惯例,没有字节序问题,以及英文和西文符号比较多的场景下(如 HTML/XML),编码较短的优点,UTF-8 编码比 UTF-16 编码应用更为广泛。

UTF-16和UCS-2的区别与联系。 第一个Unicode平面(码位从U+0000至U+FFFF)包含了最常用的字符。UTF-16与UCS-2编码这个范围内的码位需要16比特长的单个码元,数值等价于对应的码位。BMP中的这些码位是仅有的可以在UCS-2中表示的码位。但是对于BMP外的其它辅助平面字符,UCS-2却无法表示,但是UTF-16用1个或者2个16位长的码元来表示,既可以容纳UCS-2,也可以表示辅助平面字符,因此UTF-16可看成是UCS-2的父集。在没有辅助平面字符前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2字节的字集。

8.3UTF-32

UTF-32是采用定长4字节来表示Unicode字符,不同于其它的Unicode转换格式,UTF-8和UTF-16则使用不定长度编码。UTF-32在实际应用中也很少被使用,因为UTF-32对每个字符都使用4字节,就空间而言,是非常低效的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。

UTF-32与UCS-4关系。 通用字符集(UCS)的每一个字符,会在0到0x7FFFFFFF这样的字码空间中,被表示成一个的31位的码值。UCS-4足以用来表示所有的Unicode的字码空间,最大码位为0x7FFFFFFF,其空间约为20亿个码位。有些人认为保留如此大的字码空间却只为了对应很小的码集是很浪费的,所以一个新的编码UTF-32被提出来了。UTF-32是一个UCS-4 的子集,使用4字节对字符编码,但是码值只从0到0x10FFFF(百万个码位),与Unicode码值一一对应。UTF-32 原本是 UCS-4 的子集,但JTC1/SC2/WG2声明,未来所有对字符的指定都将会限制在BMP及其14个补充平面。

9.Little Endian、Big Endian与BOM

字节序与大小端是伴随着多字节字符集(Multibyte Character Set,MBCS)而出现的问题。单字节编码如ASCII是不存在编码字节序问题的,每一个字节代表一个字符,但是对于Unicode多字节字符编码,如UTF-16和UTF-32,就会存在字节序的问题。例如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?

编码存储差异。 这里就要引出两个名词:LE(Little Endian)与BE(Big Endian)。 LE(Little Endian):小端字节序,意思就是一个单元在计算机中的存放时按照低位在低地址,高位在高地址的模式存放。

BE(Big Endian):大端字节序,和LE相反,是高位在低地址,低位在高地址的模式存放。

例如“汉”字的Unicode编码是U+6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是Big Endian。如果将49写在前面,就是Little endian。“Endian”这个词出自《格列佛游记》,小人国的内战就源于吃鸡蛋时究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。

我们一般将Endian翻译成字节序,将Big Endian和Little Endian称作大尾序和小尾序,或者大端序和小端序。

编码存储差异解决办法:BOM。 为了解决上面存储时字节序的问题,Unicode规范中推荐的标记字节顺序的方法是BOM(Byte Order Mark)头,意思是字节序标志头。在UCS编码中有一个叫做零宽度非换行空格(ZERO WIDTH NO-BREAK SPACE)的字符,它的编码是U+FEFF。而U+FEFF在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符零宽度非换行空格字符。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。UTF-8编码的BOM是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。Windows就是使用BOM来标记文本文件的编码方式的。通过它基本能确定编码格式和字节序。UTF相关编码的BOM如下。

UTF编码

BOM

UTF-8

EF BB BF

UTF-16 LE

FF FE

UTF-16 BE

FE FF

UTF-32 LE

FF FE 00 00

UTF-32 BE

00 00 FE FF

记事本联通乱码。 如果没有BOM只能靠猜了。软件读入文件时可以所有编码都试一下,看哪个像。另外,BOM只针对Unicode系列编码,ANSI通通不使用BOM。很显然,没有BOM难免偶然猜错。网上就流传着一个神奇的段子:打开Windows记事本,打入“联通”两个字,保存,关闭,再打开,变成了个黑块。

有人说这是因为联通得罪了微软,当然这是玩笑话。ANSI是不带BOM的,所以联通的ANSI是GBK编码,码值为:C1 AA CD A8 ,码值的查看可以通过Notepad++的插件Hex-Editor(十六进制编辑器)来显示,如下:

“联通”码值对应的二进制码值如下:

C1      110 00001
AA      10 101010
CD      110 01101
A8      10 101000

第一二字节和第三四个字节的起始部分的都是”110”和”10”,正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本误认为这是一个UTF8编码的文件,把第一个字节110和第二个字节的10去掉,我们就得到了”00001 101010”,再把各位对齐,补上前导的0,就得到了”0000 0000 0110 1010”,Unicode码值为U+006A,这是小写的字母“j”,由于字母j理应按照ASCII字符编码在UTF-8中以单字节进行编码,所以解析失败,显示两个乱码。而之后的两字节用UTF8解码之后是U+0368,查看Unicode字符表发现为字符COMBINING LATIN SMALL LETTER C,显示为极小的字母c。以上原因,导致了“联通”两个字没有办法在记事本里正常显示。

可以认为,GBK文档中的所有字符的二进制编码第一个字节在[0xC0, 0xDF],并且第二字节在[0x80,0xBF]区间时,记事本都无法确认文本的编码格式,就会按照UTF-8的格式来显示,比如“透支”二字也会出现乱码。

BOM听起来很不错,但实际是个讨厌的设计,因为它和很多协议、规范不兼容,这是题外话。

有了BOM,于是很多文本编辑软件就会有UTF-8 without BOM,UTF-16 without BOM编码格式的选项。如果不提BOM,究竟有BOM还是没有BOM?又是一个十分纠结的问题,Windows里的软件一般都默认有BOM,而其它系统都默认没有BOM,可能是因为Windows常要兼容ANSI的原因,特别依赖BOM来防止出错。

10.内码、外码与代码页(Code Page)

前面在描述相关字符编码时也涉及到内码和代码页,但没有详细展开,这里简要的说明一下。

内码与外码关系。 内码是指操作系统内部的字符编码,内码其实就是字符编码。之所以称之为内码,是因为有外码这种东西。汉字输入码(外码)是指用户从键盘上键入汉字时所使用的汉字编码,计算机内部存储的就是汉字的内码。

常用的输入码有: 数字编码-区位码; 拼音编码-全拼、双拼、微软拼音输入法、自然码、智能ABC、搜狗等等; 字形编码-五笔、表形码、郑码输入法等。

早期操作系统的内码是与语言相关的,现在的Windows在内部统一使用Unicode,然后用代码页适应各种语言,”内码”的概念就比较模糊了。我们一般将缺省代码页指定的编码说成是内码。内码这个词汇,并没有什么官方的定义。代码页也只是微软的一种习惯叫法。作为程序 员,我们只要知道它们是什么东西,没有必要过多地考证这些名词。

代码页是什么? 目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的编码,而全部改用Unicode。于是Windows使用代码页(code page)来标识各个国家和地区字符编码,所以代码页就是字符编码的代号。例如Windows系统中,GB2312对应的code page是CP20936,BIG5的code page是CP950,GBK对应的code page是CP936。GB18030对应的code page:CP54936。

11.后记

这篇杂谈的所谈论的内容实在是太庞杂了,坚持了四天,终于初步完成了本篇博文。字符编码涉及的用语和概念繁多,仔细考究的话,没有几十本著作和文献应该是没法说清楚的,所以本篇短短1W多字的精炼描述,是不可能对字符编码的历史,编码方式和关系完美诠释的,只求让大家有个大致的了解,免受因对字符编码的不解而带来的痛苦和困惑。文章操之过急,参考文献不足,再加上本人水平有限,难免出现不足和错误之处,望大家批评指正,留言探讨。

这应该是我在腾讯实习的最后一篇 blog了,离开前,留下点东西,刻下我在鹅场三个多月实习的痕迹。


参考文献

[1]为什么计算机能读懂1和0? [2]latin1.百度百科 [3]ISO-8859-1.维基百科 [4]ANSI编码.百度百科 [5]Unicode.维基百科 [6]Unicode字符平面映射.维基百科 [7]谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词 [8]通用字符集.维基百科 [9]UTF-16/UCS-2.维基百科 [10]UTF-32.wikipedia [11]UTF-8.维基百科 [12]UTF-8.wikipedia [13]UTF-16.维基百科 [14]UTF-16.wikipedia [15]UTF-32.wikipedia [16]遇到乱码不怕不怕啦——计算机字符详尽讲解 [17]GB2312.维基百科 [18]汉字内码扩展规范.维基百科 [19]彻底搞懂字符编码(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian…) [20]windows内码、外码、字符映射表

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++11 Unicode支持

    在C++98中,为了支持Unicode字符,使用wchar_t类型来表示“宽字符”,但并没有严格规定位宽,而是让wchar_t的宽度由编译器实现,因此不同的编译...

    Dabelv
  • C#常见转义字符

    ·一种特殊的字符常量; ·以反斜线"\"开头,后跟一个或几个字符。 ·具有特定的含义,不同于字符原有的意义,故称“转义”字符。 ·主要用来表示那些用一般...

    Dabelv
  • C++构造函数体内赋值与初始化列表的区别

    问题描述下: Linux环境运行,使用g++编译,贴上如下代码出错处已标注于代码中。 代码如下:

    Dabelv
  • 如何使用StreamSets实时采集Kafka中嵌套JSON数据并写入Hive表

    Fayson
  • Html编码(&#数字型)与解码小结 - 针对Puny Code(中文域名)的解码处理

      学习并了解到Html编码的知识,源于工作中的产品需求。如果一个URL里面包含Puny Code(不仅仅指中文,还可能是韩文等Unicode里非英文的国家文字...

    宋凯伦
  • 从Java String实例来理解ANSI、Unicode、BMP、UTF等编码概念

    一切的谜都解开了!在写这篇随笔之前,我的心情只能用金田一每次破案后的这句台词来表达。

    哲洛不闹
  • react核心api

    react从16年12月开始,已经学了有2年多了。react引导了作者找到了第一份比较专职的前端工作。react 2014年横空出世,以其革命性的写法,带动了前...

    一粒小麦
  • RocketMq之Broker源码分析

    服务器上部署的RocketMq进程一般称之为Broker,Broker会接收Producer的消息,持久化到本地,然后push给Consumer,通常使用集群部...

    大王叫下
  • python编码问题

    我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。

    bear_fish
  • 一张刮刮卡竟包含这么多前端知识点

    刮刮卡是大家非常熟悉的一种网页交互元素了。实现刮涂层的效果,需要借助canvas来实现,想必每个前端工程师都清楚。实现刮刮卡并不难,但其中却涉及很多知识点,掌握...

    Nealyang

扫码关注云+社区

领取腾讯云代金券