字符编码详解

  相信很多程序员在面对“字符编码”这个问题时,都曾困扰过,甚至头疼不已。接着之前两篇博客(二进制文件和文本文件文本文件换行符),本文将对“字符编码”做一个全面而细致的剖析,力求做到,让每位读者在读完此文后能有一种醍醐灌顶的感觉。


1.基础知识

  我们在计算机屏幕上看到的是实体化的字符,而计算机存储介质中存放的实际上是二进制的比特流。这两者之间有一个转换规则,类比密码学中的加密解密,从“字符”到“比特流”称之为“编码”,从“比特流”到“字符”则称之为“解码”。这也引出了下面我们要讲的几个概念。

1.1.字符集

  字符(Character)是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。 字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。那么为什么会有那么多字符集标准呢?这是因为,很多规范和标准在最初制定时并没有意识到这将会是以后全球普适的准则,或者出于组织本身利益考虑想从本质上区别于现有标准。于是,就产生了那么多具有相同效果但又互不兼容的标准。

1.2.字符编码

  字符编码(Character encoding),是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。一般而言,会直接将字符在字符集中的位置,或者说是码点(code point),作为编码后的值。故而,“字符集”和“字符编码”二者是紧密耦合的(也有例外,后面我们会讲到)。因此,某种字符集也可以说成是某种字符编码方式,例如,当我们说到“ASCII”时,既可以指代ASCII字符集,也可以指代ASCII编码。

2.常用字符集&字符编码

2.1.ASCII

  ASCII(American Standard Code for Information Interchange,美国信息互换标准编码)是基于基础拉丁字符的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII(Extended ASCII)则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
  ASCII用7bit来编码字符,共128个码位,由于计算机1个字节是8bit,所以最高位为0,即00000000-01111111(0x00-0x7F)。其中有95(十进制32-126)个可打印字符,包括常用的字母、数字、标点符号等,另外还有33(十进制0-31及127)个控制字符。ASCII字符编码对应规则如下:


这里写图片描述

  ASCII是美国人设计的,只能支持基础拉丁字符,而当计算机发展到欧洲,欧洲其它不只是用基础拉丁字符的国家(即用更大的派生拉丁字符集)该怎么办呢?最简单的办法就是将ASCII没有用到的第8位也用上,这样能表达的字符个数就达到了256个,相较原来,增长了一倍, 这就是EASCII。EASCII基本解决了整个西欧的字符编码问题。但是对于欧洲其它地方如北欧,东欧地区,256个字符还是不够用,因此出现了ISO 8859。为解决256个字符不够用的问题,ISO 8859采取的不再是单个独立的编码规则,而是由一系列的字符集(共15个)所组成,分别称为ISO 8859-n(n=1,2,3…11,13…16,没有12)。其每个字符集对应不同的语言,如ISO 8859-1对应西欧语言,ISO 8859-2对应中欧语言等。其中大家所熟悉的Latin-1就是ISO 8859-1的别名,它表示整个西欧的字符集范围。需要注意的的是,ISO 8859-n与ASCII是兼容的,即其0000000-01111111(0x00-0x7f)范围段与ASCII保持一致,而10000000-11111111(0x80-0xFF)范围段被扩展用到不同的字符集。EASCII字符编码对应规则如下:(含表格符号、计算符号、希腊字母和特殊的拉丁符号等)

这里写图片描述

2.2.中文编码

  为了扩充ASCII,以用于显示本国的语言,不同的国家和地区制定了不同的标准,由此产生了GB2312(简体中文),BIG5(繁体中文),JIS(日文)等各自的编码标准。这一节我们将集中叙述几种常用的中文编码标准。

2.2.1.GB2312

  为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字字符集国家标准编码,统称为GB码,或国标码。其中最有影响的是于1980年发布的《信息交换用汉字编码字符集·基本集》,标准号为GB 2312-1980。GB2312通行于我国内地,新加坡等地也采用此编码标准,并且几乎所有的中文系统和国际化的软件都支持GB2312。
  GB2312是一个简体中文字符集,收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,含一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。GB2312对所收录字符进行了“分区”处理,共94个区,区从1(十进制)开始,一直到94(十进制),每区含有94个位,位从1(十进制)开始,一直到94(十进制),共8836(94 * 94)个码位。由此,每个字符都能找到其唯一对应的区位和码位,这种表示方式也称为区位码。各区具体说明如下:

01-09区收录除汉字外的682个字符,有164个空位(9 * 94 - 682)
10-15区为空白区,没有使用
16-55区收录3755个一级汉字(简体),按拼音排序
56-87区收录3008个二级汉字(简体),按部首/笔画排序
88-94区为空白区,没有使用

  GB2312以区位码为基础,对字符采用双字节编码,其中高字节表示区码,低字节表示位码。由于区码和位码的取值范围均在1-94之间,此范围同ASCII的编码范围冲突。例如汉字‘珀’在GB2312中的区位码为7174(十进制),其双字节表示形式为71、74;而两个ASCII字符‘GJ’的存储码也是71、74,这种冲突将导致解码时的混乱。为解决这个问题,GB2312将区位码均加上0xA0,这样高低字节的第8位都变成了1,进而同ASCII区分开来。具体操作如下图:


这里写图片描述

例如,‘李’字的区位码为3278(表示在32区,78位),按照上图步骤获取其GB2312编码:

32(区)转化为十六进制为20;
加上A0为C0;
将78(位)转化为十六进制为4E;
加上A0为EE;
组合区和位,为C0EE;
得到'李'字的GB2312编码为C0EE。

  所以说,GB2312是兼容ASCII的一种编码方式:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(高字节)范围从0xA1到0xF7,后面一个字节(低字节)范围从0xA1到0xFE,这样我们就可以组合出7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127以下的那些就叫”半角”字符了。
  有人可能曾困惑过:GB2312采用双字节编码,原则上来说共有65536个码位,为何实际却只收录字符7445个?现在,我想答案应该是显而易见的:出于兼容ASCII和节省存储容量的考虑,GB2112的双字节编码是变长的,有些字符是单字节表示,如ASCII字符,有些字符是双字节表示,如汉字,由此而来的代价就是损失一部分码位;而且,编码的设计也并非想象的那样,所有字符从头到尾布满整个二维表,其中还预留有一部分空间以作他用。
  GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。然而,对于人名、古汉语等方面出现的罕用字,GB2312却不能处理,这就导致了后来GBK及GB18030的出现。

2.2.2.BIG5

  在台湾、香港与澳门地区,使用的是繁体中文字符集。而1980年发布的GB2312面向简体中文字符集,并不支持繁体汉字。在这些使用繁体中文字符集的地区,一度出现过很多不同厂商提出的字符集编码,这些编码彼此互不兼容,造成了信息交流的困难。为统一繁体字符集编码,1984年,台湾五大厂商宏碁、神通、佳佳、零壹以及大众一同制定了一种繁体中文编码方案,即Big5,又称大五码。
  大五码是一种繁体中文汉字字符集,其中繁体汉字13053个,808个标点符号、希腊字母及特殊符号。大五码采用双字节编码,第1字节范围0x81-0xFE,避开了同ASCII码的冲突,第2字节范围是0x40-0x7E和0xA1-0xFE。因为Big5的字符编码范围同GB2312存在冲突,所以二者并不兼容。Big5字符编码分布表如下:

编码范围(十六进制)符号类别
8140-A0FE保留(用作造字区)
A140-A3BF标点符号、希腊字母及特殊符号
A3C0-A3FE保留(未开放用于造字区)
A440-C67E常用汉字(先按笔划,再按部首排序)
C6A1-C8FE保留(用作造字区)
C940-F9D5非常用汉字(先按笔划,再按部首排序)
F9D6-FEFE保留(用作造字区)


  Big5编码推出后,得到了繁体中文软件厂商的广泛支持,在使用繁体汉字的地区迅速普及使用。目前,Big5编码在台湾、香港、澳门及其他海外华人中普遍使用,成为了繁体中文编码的事实标准。在互联网中检索繁体中文网站,所打开的网页中,大多都是通过Big5编码产生的文档。
  不过,尽管Big5码内包含一万多个字符,但是没有考虑社会上流通的人名、地名用字、方言用字、化学及生物科等用字,亦没有包含日文平假名及片假名字母。

2.2.3.GBK

  GBK即汉字内码扩展规范,K为扩展的汉语拼音中“扩”字的声母。英文全称Chinese Internal Code Specification。GBK编码标准兼容GB2312,是对GB2312-80的扩展,简、繁体字融于一库。GBK采用双字节表示,总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。总计23940 个码位,共收入21886个字符,其中汉字(包括部首和构件)21003 个,图形符号883 个。GBK同样兼容ASCII,00–7F范围内单字节表示ASCII字符。

2.2.4.GB18030

  GB18030,全称GB18030-2000《信息交换用汉字编码字符集基本集的扩充》,是我国政府于2000年3月17日发布的新的汉字编码国家标准,2001年8月31日后在中国市场上发布的软件必须符合本标准。
  GB18030字符集标准解决了汉字、日文假名、朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。该标准的字符总编码空间超过150万个编码位,收录了27484个汉字,覆盖中文、日文、朝鲜语和中国少数民族文字。满足中国大陆、香港、台湾、日本和韩国等东亚地区信息交换多文种、大字量、多用途、统一编码格式的要求。
  GB18030标准采用单字节、双字节和四字节三种方式对字符编码,兼容ASCII、GB2312和GBK。

2.2.5.小结


这里写图片描述

3.Unicode

3.1.Unicode字符集

  虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:在一份文档中显示世界上所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是Unicode字符集。
  Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2017年6月20日公布的10.0.0,已经收录超过十万个字符(第十万个字符在2005年获采纳)。Unicode发展由非营利机构统一码联盟(The Unicode Consortium)负责,该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限,亦不适用于多语环境。Unicode备受认可,并广泛地应用于电脑软件的国际化与本地化过程。
  Unicode字符集包含了可能出现的所有字符,每个字符对应一个数字,这个数字即码点(Code Point),如字符‘H’的码点为72,字符‘李’的码点为26446。Unicode包含了1114112个码点,即0x000000-0x10FFFF。世界上所有字符都可以在Unicode字符集中找到对应的唯一码点(点击这里,查询字符对应的码点)。
  Unicode按照使用上的频繁度将码空间划分为17个平面,00-10(十六进制,最高两位),即从0 - 16(十进制),每个平面有65536个码点(2^16),其中最重要的是第一个Unicode平面(码位0x0000-0xFFFF),包含了最常用的字符,该平面被称为基本多语言平面(Basic Multilingual Plane),缩写为BMP,其他平面称为辅助平面(Supplementary Planes),要么是用来表示一些远古时期的文字,要么是留作扩展。Unicode字符集中各个平面的基本情况如下:


这里写图片描述

  在表示一个Unicode字符时,通常会用“U+”然后紧接着一组十六进制的数字。在BMP里的所有字符,要用4位十六进制数(例如U+4AE0,共支持六万多个字符);在BMP以外的字符则需要使用5位或6位十六进制数了。
  事实上,历史上存在两个独立的创立单一字符集的尝试,除了统一码联盟的Unicode项目,还有一个是国际标准化组织(ISO)的 ISO 10646项目,该项目定义的标准字符集称作UCS(Universal Character Set,通用字符集;又称Universal Multiple-Octet Coded Character Set,通用多八位编码字符集)。后来这二者的工作成果合并了,现今,两个项目仍都独立存在,并独立地公布各自的标准。具体细节,读者可自行了解,在这里我们只需要知道,目前实际应用的Unicode版本对应于UCS-2,最新(但未实际广泛使用)的Unicode版本对应于UCS-4。UCS-2,只定义了BMP内的字符,UCS-4,定义了由0x00000000到0x7FFFFFFF的字符(四字节,符号位未使用且零)。
  Unicode只是一个字符集,只规定了字符所对应的码点,并没有指定字符编码方式,也即从码点到用来存储的比特流之间如何映射。关于这一点,我们不妨回忆一下之前曾经讲过的:一般而言,会直接将字符在字符集中的位置,或者说是码点,作为编码后的值。在这种方式下,字符集和字符编码耦合紧密,限制了字符集的扩展能力。鉴于此,Unicode将字符集和字符编码方案分离开,从字符到码点,再从码点到编码值,作了两次映射。这样,虽然每个字符在Unicode字符集中都能找到唯一确定的码点,但是最终的编码值却是由具体的编码方案决定。例如,同样是对字符“A”进行编码,UTF-8编码得到的值是0x41,而UTF-16(Big-Endian)得到的却是0x0041。

3.2.编码方案

  Unicode的编码方案又叫Unicode转换格式,简称为UTF(Unicode Transformation Format),包括UTF-16、UTF-32以及UTF-8,其中又属UTF-8使用最为广泛。

3.2.1.UTF-16

  UTF-16对应于UCS-2,采用双字节编码BMP内位于U+0000至U+D7FF以及U+E000至U+FFFF字符,编码后的值等同于对应的码点;由于BMP内的U+D800至U+DFFF码位不对应于任何字符,UTF-16借助于这些码位,根据一定的规则,采用四字节编码辅助平面内位于U+10000至U+10FFFF的字符。因此,UTF-16是一种变长编码方式。
  UTF-16可看成是UCS-2的父集。在没有辅助平面字符前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2字节的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。
  UTF-16编码分大端序(Big-Endian,简称UTF-16 BE)和小端序(Little-Endian,简称UTF-16 LE)两种,区别在于字节序的不同。例如,汉字“奎”的Unicode码点是594E,“乙”的Unicode码点是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?UTF-16采用BOM(Byte Order Mark,字节序标记)机制来解决这个问题:在Unicode中有两个特殊字符,一个是U+FEFF,表示”ZERO WIDTH NO-BREAK SPACE”;另一个是U+FFFE,它在Unicode中是不存在的字符,所以不应该出现在实际传输中。UTF-16在传输字节流前,先传输一个BOM,这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。
  以下的例子有四个字符:U+6731(朱)、U+002C(,)、U+807F(聿)、U+2A6A5(四个“龍”组成,无法显示)。


这里写图片描述

3.2.2.UTF-32

  UTF-32对应于UCS-4,采用四字节编码所有的Unicode字符。

3.2.3.UTF-8

  UTF-8是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字的应用中,优先采用的编码。
 UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节):

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


这里写图片描述

  总结一下就是:1.对于单字节编码字符,字节的第一位设为0,后面7位为该字符的二进制Unicode码点值,故而UTF-8兼容ASCII;2.对于n字节编码字符(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10,剩下的二进制位,全部用该字符的二进制Unicode码点值替代。
  来看个例子。字符“汉”表示为U+6C49,6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001(不足16位,前面补0), 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,转换为十六进制,即UTF-8编码为E6 B1 89。

UTF-8编码的优点:
1. ASCII是UTF-8的一个子集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
2. UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0、C1、F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值。
UTF-8编码的缺点:
1. 与其他Unicode编码相比,特别是UTF-16,在UTF-8中ASCII字符占用的空间只有一半,可是在一些字符的UTF-8编码占用的空间就要多出1/3,特别是中文、日文和韩文(CJK)这样的方块文字。

  由于在UTF-8编码中,其自身已经带了控制信息,如1110xxxx 10xxxxxx 10xxxxxx ,其中1110就起到了控制作用,所以不需要额外的BOM机制,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是0xEFBBBF,所以如果接收者收到以0xEFBBBF开头的字节流,就知道这是UTF-8编码了。但是需要注意,不是所有软件或者程序都能正确处理BOM标记,所以我们不推荐对UTF-8编码的文件加上BOM。

4.遗留问题

4.1.ANSI编码

  ANSI字面意思是“美国国家标准协会”(American National Standards Institute),在Windows系统的编码处理中,一般用ANSI来代表系统默认编码方式,而且并不是确定的某一种编码方式——在简体中文操作系统中ANSI编码默认指的是GB系列编码(GB2312、GBK、GB18030);在繁体中文操作系统中ANSI编码默认指的是BIG5;在日文操作系统中ANSI编码默认指的是Shift JIS,等等。


参考文献

[1] https://baike.baidu.com/item/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81/8446880?fr=aladdin
[2] http://blog.jobbole.com/84903/
[3] http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
[4] http://pcedu.pconline.com.cn/empolder/gj/other/0505/616631_all.html#content_page_2
[5] http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
[6] http://blog.jobbole.com/76376/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
[7] http://blog.jobbole.com/74109/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
[8] http://www.51hei.com/mcu/4342.html
[9] http://www.cnblogs.com/leesf456/p/5317574.html
[10] http://polaris.blog.51cto.com/1146394/377468/
[11] https://baike.baidu.com/item/%E5%85%A8%E8%A7%92/9323113?fr=aladdin
[12] http://www.jianshu.com/p/bd7a6c508c33
[13] http://blog.csdn.net/normallife/article/details/5503773
[14] https://baike.baidu.com/item/%E5%AD%97%E7%AC%A6%E9%9B%86
[15] https://zh.wikipedia.org/wiki/UTF-8
[16] https://zh.wikipedia.org/wiki/Unicode
[17] https://zh.wikipedia.org/wiki/UTF-32
[18] https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%AD%97%E7%AC%A6%E9%9B%86
[19] https://zh.wikipedia.org/wiki/UTF-16
以上为本文的全部参考文献,对原作者表示感谢。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值