字符编码
为什么要先说说字符,因为计算机只认识0和1。所以字符的重要性不言而喻。
参考资料
字符编码常识及问题解析
在面试的笔试题里出了一道开放性的题:请简述Unicode与UTF-8之间的关系。一道看似简单的题,能给出满意答案的却寥寥无几 ,确实挺失望的。所以今天就结合我以前做过的一个关于字符编码的分享,总结一些与字符编码相关的知识和问题。如果你这方面的知识已经掌握的足够了,可以忽略这篇文字。但如果你没法很好的回答我上面的面试题,或经常被乱码的问题所困扰,还是不妨一读。
基本常识
1.位和字节
说起编码,我们必须从最基础的说起,位和字节(别觉得这个过于简单不值一说,我还真见过很多个不能区分这两者的程序员)。位(bit)是指计算机里存放的二进制值(0\/1),而8个位组合成的“位串”称为一个字节,容易算出,8个位的组合有256( 28 )个组合方式,其取值范围是“00000000-11111111”,常用十六进制来表示。比如“01000001”就是一个字节,其对应的十六进制值为“0x41”。
而我们通常所讲的字符编码,就是指定义一套规则,将真实世界里的字母\/字符与计算机的二进制序列进行相互转化。如我们可以针对上面的字节定义如下的转换规则:
01000001(0x41)<-> 65 <-> 'A'
即用字位序“01000001”来表示字母’A’。
2.拉丁字符
拉丁字符是当今世界使用最广泛的符号了。通常我们说的拉丁字母,指的的是基础拉丁字母,即指常见的”ABCD“等26个英文字母,这些字母与英语中一些常见的符号(如数字,标点符号)称为基础拉丁字符,这些基础拉丁字符在使用英语的国家广为流行,当然在中国,也被用来当作汉语拼音使用。在欧洲其它一些非英语国家,为满足其语言需要,在基础拉丁字符的基础上,加上一些连字符,变音字符(如’Á’),形成了派生拉丁字母,其表示的字符范围在各种语言有所不同,而完整意义上的拉丁字符是指这些变体字符与基础拉丁字符的全集。是比基础拉丁字符集大很多的一个集合。
编码标准
前文提到,字符编码是一套规则。既然是规则,就必须有标准。下面我就仔细说说常见的字符编码标准。
1.拉丁编码
ASCII的全称是American Standard Code for Information Interchange(美国信息交换标准代码)。顾名思义,这是现代计算机的发明国美国人设计的标准,而美国是一个英语国家,他们设定的ASCII编码也只支持基础拉丁字符。ASCII的设计也很简单,用一个字节(8个位)来表示一个字符,并保证最高位的取值永远为’0’。即表示字符含义的位数为7位,不难算出其可表达字符数为27 =128个。这128个字符包括95个可打印的字符(涵盖了26个英文字母的大小写以及英文标点符号能)与33个控制字符(不可打印字符)。例如下表,就是几个简单的规则对应:
字符类型 字符 二进制 16进制 10进制 可打印字符 A 01000001 0x41 65 可打印字符 a 01100001 0x61 97 控制字符 \r 00001101 0x0D 13 控制字符 \zn 00001010 0xA 10 前面说到了,ASCII是美国人设计的,只能支持基础拉丁字符,而当计算机发展到欧洲,欧洲其它不只是用的基础拉丁字符的国家(即用更大的派生拉丁字符集)该怎么办呢?
当然,最简单的办法就是将美国人没有用到的第8位也用上就好了,这样能表达的字符个数就达到了28 =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(0x00)-01111111(0x7f)范围段与ASCII保持一致,而10000000(0x80)-11111111(0xFF)范围段被扩展用到不同的字符集。
2.中文编码
以上我们接触到的拉丁编码,都是单字节编码,即用一个字节来对应一个字符。但这一规则对于其它字符集更大的语言来说,并不适应,比如中文,而是出现了用多个字节表示一个字符的编码规则。常见的中文GB2312(国家简体中文字符集)就是用两个字节来表示一个汉字(注意是表示一个汉字,对于拉丁字母,GB2312还是是用一个字节来表示以兼容ASCII)。我们用下表来说明各中文编码之间的规则和兼容性。
对于中文编码,其规则实现上是很简单的,一般都是简单的字符查表即可,重要的是要注意其相互之间的兼容性问题。如如果选择BIG5字符集编码,就不能很好的兼容GB2312,当做繁转简时有可能导致个别字的冲突与不一致,但是GBK与GB2312之间就不存在这样的问题。
3.Unicode
以上可以看到,针对不同的语言采用不同的编码,有可能导致冲突与不兼容性,如果我们打开一份字节序文件,如果不知道其编码规则,就无法正确解析其语义,这也是产生乱码的根本原因。有没有一种规则是全世界字符统一的呢?当然有,Unicode就是一种。为了能独立表示世界上所有的字符,Unicode采用4个字节表示一个字符,这样理论上Unicode能表示的字符数就达到了231 = 2147483648 = 21 亿左右个字符,完全可以涵盖世界上一切语言所用的符号。我们以汉字”微信“两字举例说明:
微 <-> \u5fae <-> 00000000 00000000 01011111 10101110
信 <-> \u4fe1 <-> 00000000 00000000 01001111 11100001
容易从上面的例子里看出,Unicode对所有的字符编码均需要四个字节,而这对于拉丁字母或汉字来说是浪费的,其前面三个或两个字节均是0,这对信息存储来说是极大的浪费。另外一个问题就是,如何区分Unicode与其它编码这也是一个问题,比如计算机怎么知道四个字节表示一个Unicode中的字符,还是分别表示四个ASCII的字符呢?
以上两个问题,困扰着Unicode,让Unicode的推广上一直面临着困难。直至UTF-8作为Unicode的一种实现后,部分问题得到解决,才得以完成推广使用。说到此,我们可以回答文章一开始提出的问题了,UTF-8是Unicode的一种实现方式,而Unicode是一个统一标准规范,Unicode的实现方式除了UTF-8还有其它的,比如UTF-16等。
话说当初大牛Ben Thomson吃饭时,在一张餐巾纸上,设计出了UTF-8,然后回到房间,实现了第一版的UTF-8。关于UTF-8的基本规则,其实简单来说就两条(来自阮一峰老师的总结):
规则1:对于单字节字符,字节的第一位为0,后7位为这个符号的Unicode码,所以对于拉丁字母,UTF-8与ASCII码是一致的。
规则2:对于n字节(n>1)的字符,第一个字节前n位都设为1,第n+1位为0,后面字节的前两位一律设为10,剩下没有提及的位,全部为这个符号的Unicode编码。
通过,根据以上规则,可以建立一个Unicode取值范围与UTF-8字节序表示的对应关系,如下表,
举例来说,’微’的Unicode是’\u5fae’,二进制表示是”00000000 00000000 01011111 10101110“,其取值就位于’0000 0800-0000 FFFF’之间,所以其UTF-8编码为’11100101 10111110 10101110’ (加粗部分为固定编码内容)。
通过以上简单规则,UTF-8采取变字节的方式,解决了我们前文提到的关于Unicode的两大问题。同时,作为中文使用者需要注意的一点是Unicode(UTF-8)与GBK,GB2312这些汉字编码规则是完全不兼容的,也就是说这两者之间不能通过任何算法来进行转换,如需转换,一般通过GBK查表的方式来进行。
常见问题及解答
1.windows Notepad中的编码ANSI保存选项,代表什么含义?
ANSI是windows的默认的编码方式,对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。所以,如果将一个UTF-8编码的文件,另存为ANSI的方式,对于中文部分会产生乱码。
2.什么是UTF-8的BOM?
BOM的全称是Byte Order Mark,BOM是微软给UTF-8编码加上的,用于标识文件使用的是UTF-8编码,即在UTF-8编码的文件起始位置,加入三个字节“EE BB BF”。这是微软特有的,标准并不推荐包含BOM的方式。采用加BOM的UTF-8编码文件,对于一些只支持标准UTF-8编码的环境,可能导致问题。比如,在Go语言编程中,对于包含BOM的代码文件,会导致编译出错。详细可见我的这篇文章。
3.为什么数据库Latin1字符集(单字节)可以存储中文呢?
其实不管需要使用几个字节来表示一个字符,但最小的存储单位都是字节,所以,只要能保证传输和存储的字节顺序不会乱即可。作为数据库,只是作为存储的使用的话,只要能保证存储的顺序与写入的顺序一致,然后再按相同的字节顺序读出即可,翻译成语义字符的任务交给应用程序。比如’微’的UTF-8编码是’0xE5 0xBE 0xAE’,那数据库也存储’0xE5 0xBE 0xAE’三个字节,其它应用按顺序从数据库读取,再按UTF-8编码进行展现。这当然是一个看似完美的方案,但是只要写入,存储,读取过程中岔出任何别的编码,都可能导致乱码。
4.Mysql数据库中多个字符集变量(其它数据库其实也类似),它们之间分别是什么关系?
我们分别解释:
character_set_client:客户端来源的数据使用的字符集,用于客户端显式告诉客户端所发送的语句中的的字符编码。
character_set_connection:连接层的字符编码,mysql一般用character_set_connection将客户端的字符转换为连接层表示的字符。
character_set_results:查询结果从数据库读出后,将转换为character_set_results返回给前端。
而我们常见的解决乱码问题的操作:
MySQL
mysql_query\('SET NAMES GBK'\)
其相当于将以上三个字符集统一全部设置为GBK,这三者一致时,一般就解决了乱码问题。
character_set_database:当前选中数据库的默认字符集,如当create table时没有指定字符集,将默认选择该字符集。
character_set_database已经character_set_system,一般用于数据库系统内部的一些字符编码,处理数据乱码问题时,我们基本可以忽略。
5.什么情况下,表示信息丢失?
对于mysql数据库,我们可以通过hex(colname)函数(其它数据库也有类似的函数,一些文本文件编辑器也具有这个功能),查看实际存储的字节内容,如:
通过查看存储的字节序,我们可以从根本上了解存储的内容是什么编码了。而当发现存储的内容全部是’3F’时,就表明存储的内容由于编码问题,信息已经丢失了,无法再找回。
之所以出现这种信息丢失的情况,一般是将不能相互转换的字符集之间做了转换,比如我们在前文说到,UTF-8只能一个个字节地变成Latin-1,但是根本不能转换的,因为两者之间没有转换规则,Unicode的字符对应范围也根本不在Latin-1范围内,所以只能用’?(0x3F)’代替了。
总结:
本文从基础知识与实际中碰到的问题上,解析了字符编码相关内容。而之所以要从头介绍字符编码的基础知识,是为了更好的从原理上了解与解决日常碰到的编码问题,只有从根本上了解了不同字符集的规则及其之间的关系与兼容性,才能更好的解决碰到的乱码问题,也能避免由于程序中不正确的编码转换导致的信息丢失问题。
人机交互之字符编码
编码其实并不神秘,可以说是随处可见。上溯到远古时期,猿人为了记录所见到的事物,就在岩壁刻下简单的图形,这些图形可以说是最古老的一种编码了。我们的汉语其实也是一种编码,李白同学为了描述瀑布美景,就写出了“飞流直下三千尺,疑是银河落九天”的千古名句。随着汉语的不断完善,我们遇见的每件具体物体和大部分抽象的概念都有了编码,比如“树”代表长有绿叶的高高的植被,“开心”代表了一种愉悦的精神状态。当然了,还有英语、法语、德语等等,它们每一个都是一种编码,可以表达自然万物以及抽象概念。
千百年来,文字这种编码足以应付我们的生存所需,直到计算机的出现。那么计算机有什么特别之处呢?这就要从计算机的诞生说起,可以说计算机的诞生主要归功于布尔、香农、图灵、冯·诺依曼。
布尔创立逻辑代数学,为数字电子计算机的二进制、开关逻辑元件和逻辑电路的设计辅平了道路。1854年,出版了名著《布尔代数》,后来发展成为现代计算机的理论基础——数理逻辑。
香农在其一篇硕士论文中指出,能够用二进制系统表达布尔代数中的逻辑关系,用“1”代表“真True”,用“0”代表“假False”,并由此用二进制系统来构筑逻辑运算系统。
1936年,图灵在论文《论可计算数及在密码上的应用》中,严格地描述了计算机的逻辑结构,首次提出了计算机的通用模型——“图灵机”,并从理论上证明了这种抽象计算机的可能性。
冯·诺依曼提出计算机基本结构和工作方式的设想,理论要点就是:数字计算机的数制采用二进制;计算机应该按照程序顺序执行。人们把冯诺依曼的这个理论称为冯诺依曼体系结构,所有的的计算机都采用的是冯诺依曼体系结构。
由此,我们知道计算机内部只有0和1两个数,无论是什么样的信息,在计算机内部都是用0和1来表示。尽管计算机内部用0和1来表达万物,但是计算机与外部的交互仍然采用人们熟悉和便于阅读的方式,期间的转换过程可以简单的称为编码、解码。
![]()
计算机中用的最多的就是字符,所以这里会详细讲述
字符编码
。在讲述前,先来看几个概念:
Character(字符)): 计算机中,字符包括字素,类似字素的单元,可书写语言中的字母表、音节表等。例如:字母,从0到9的数字,常用标点符号,空白符,控制符等。中文的你、我、他,日文的
に、ほ、ん、ご
也都是字符。Grapheme(字素):书写文字的最小单元,类似于我们语言中的phonemes(音素)。
Glyph(字形):字素的表现形式,下图为a的不同字形表示(图片来自wiki)。
![]()
最开始的计算机不像现在的这般强大,老式计算机不能制作、浏览图片,不能观看视频,只能用来简单地操作字符。这时就需要一套方案规定字符在计算机内部如何表示。
开天辟地:ASCII码
60年代,美国制定了一套字符编码标准,对应英语中用到的字符和相应的二进制表示。这套标准被称为ASCII码,它一共规定了128个字符的编码,每个字符均用8个二进制位表示(最高位均为0),这128个字符包括:
32个
控制字符
:十进制(0~31),比如ESC (escape)
,二进制表示为00011011。
空格(space)
,二进制表示为00100000;DEL (delete)
,二进制表示为01111111。标点以及运算符:(33~47,58~64,91~96,123~126),比如‘+’表示为00101011。
数字(48~57),大写字母(65~90),小写字母(97~122)。比如‘a’表示为01100001。
百花齐放:各种编码
英语字符用128个符号编码就足够了,但是其他语言仅用这128个就不一定够了,比如法语中的é就无法用ASCII表示。于是,一些欧洲国家决定充分利用ASCII码中闲置的最高位,这样法语中的é可以编码为10000010。这样,这些欧洲国家的编码体系最多支持2^8=256个字符。
但是这样似乎是饮鸩止渴,不同的国家都有不同字符,如果他们都是利用ASCII码的最高位来扩展能表达的字符个数,就会遇见编码相同但代表字符不同的情况。比如法语中编码10000010代表é,而在希伯来语编码中却代表了字母Gimel (ג),在俄语中又会代表另一个字符。
另外,许多国家的字符数太过于庞大了,比如汉字就多达10万左右。这个时候必须使用多个字节(一个字节8个bit)。简体中文常见的编码方式GB2312就是使用两个字节表示一个汉字,所以理论上可以表示2^16=65536个汉字。在本文的最后部分将简单介绍中文编码方案
一统天下:Unicode字符集
在百花齐放的年代,各个国家之间没有一个统一的编码,导致同样的二进制串可以被解释成不同的符号。因此我们在打开文本文件时,必须要知道它的编码方式,不然就得不到自己想要的信息,呈现在我们眼前的将是一堆毫无意义的字符,这就是所谓的乱码。
更糟糕的是不能在一个文件里同时使用不同语言的字符。如果我想同时使用中文和日文,那么文件编码设为日文编码(常用的为Shift-JIS、EUC-JP)的话,就不能涵盖中文,如果设为中文编码的话,就不能使用日文。
于是人们急需要一种包罗万象的编码方式,这种编码最好能够涵盖世界上所有的符号。这时候,Unicode字符集应运而生,最初人们天真地认为用2个字节(16位,65536个码值)就可以表示世界上所有语言的文字符号。但是当初这个想法太过于草率了,因为东亚(中日韩)字符非常多,65536个字符并不够表示所有字符。所以Unicode规范进行了扩编,截止2014.6.16,最新版本的Unicode 7.0.0包含了超过110000个字符的编码。
Unicode字符集规定了字符的编码,但不包括这些字符的各种字形的编码。Unicode定义了从0 hex 到 10FFFF hex一共1,114,112个
码值(code points)
,每个码值以U+hhhh
的形式表示,其中每一个“h”代表一个十六进制数。Unicode中有一部分保留码值,并没有定义任何字符。不过需要注意的是Unicode字符集只是规定了字符和二进制之间的对应关系,却没有规定在存储和传输时具体落实为几个字节,如何表示,所以仅有Unicode字符集是不够。
发扬光大:UTF-8编码
字符的码值是一回事,在存储和传输时,具体落实为几个字节,如何表示,又是另一回事,码值的具体表示形式,就由字符编码方式来规定。
Unicode有很多种实现方式,比如UTF-8,UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示)。互联网上使用最多的就是UTF-8,W3C建议网页中使用UTF-8作为默认的编码方式。
UTF-8是一种变长的编码方式,使用1-4个字节将Unicode中的1,112,064个码值都进行了编码。码值在Unicode中越靠前,一般使用频率就越高,UTF-8编码时使用的字节数也就越少。Unicode的前128个字符,和ASCII码一一对应,UTF-8编码也和ASCII编码一致。
总体来说,UTF-8的编码规则很简单,只有两条:
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。
对于n字节的符号(1<n<5),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了UTF-8的编码规则,字母x表示了可用编码的位。
Unicode符号范围 UTF-8编码方式 U+0000 → U+007F 0xxxxxxx U+0080 → U+07FF 110xxxxx 10xxxxxx U+0800 → U+FFFF 1110xxxx 10xxxxxx 10xxxxxx U+10000 → U+1FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx \ 依据上表解读UTF-8编码也十分容易,如果一个字节的第一位是0,则这个字节就代表了一个字符;如果一个字节的第一位是1,则从第一位起连续有几个1,就表示当前字符占有多少个字节。
下面来看一下如何根据字符的Unicode码值对其进行UTF-8编码。以汉字“学”为例,“学”的Unicode码值为U+5b66,处于上面表格的
U+0800 → U+FFFF
段内,因此UTF-8编码用三个字节来存储。然后将U+5b66从最低位到最高位依次填入到上面格式中的x,并在多出的高位上补0即可。5b66转换为二进制:0101101101100110, 填入到:1110xxxx 10xxxxxx 10xxxxxx 中的x标记位,结果如下:11100101 10101101 10100110,这样就得到了“学”的UTF-8编码,转换为16进制就是
e5ada6
。我们来简单验证一下:
>>> u'学' u'\u5b66' >>> u'学'.encode("UTF-8") '\xe5\xad\xa6'
如果我们知道了一个字符的UTF-8编码,那么同样可以得到它的Unicode码值。比如说我知道有以下UTF-8编码
e78bbc
,展开二进制为11100111 10001011 10111100,第一个字节有三个连续的1,因此这三个字节表示一个字符,字符的码值为00111001011111100,转换为16进制为U+72fc,Unicode中U+72fc即为‘狼’。同样,我们来验证一下:>>> '\xe7\x8b\xbc'.decode('utf-8') u'\u72fc' >>> print '\xe7\x8b\xbc'.decode('utf-8') 狼
百花齐放之中文编码
在前面,我们提到了gbk2312中文编码方案,下面将详细讲解一下中文编码。老外建立字符编码标准时显然没有考虑我们历史悠久的中文,只有128个字符的ASCII码无论如何也无法为我所用。于是,中国国家标准总局随后就发布了GB2312码,即中华人民共和国国家汉字信息交换用编码,并于1981年5月1日实施。GB2312字符集中除常用简体汉字字符外还包括希腊字母等可能会用到的字符,但是未收录繁体中文汉字和一些生僻字。
在我们学习GB2312的编码规则前,先来看以下几个概念:
1、
区位码
,为了便于计算机接受、辨认、处理汉字,我们为中文常用的汉字、符号、数字等编了唯一的数码,这就是区位码。区位码是由4位十进制数字组成的,因为我们把所有的国标汉字与符号组成一个94×94的矩阵,每一行称为一个区,每一列称为一个位,要表示一个汉字只需要给出行号(01~94)与列号(01-94)即可。我们可以用google搜索区位码查询系统
,就可以方便地在线查询汉字的区位码了,例如“学”字的区位码为4907。2、
国标码
,区位码无法直接用于识别汉字,因为可能与通信使用的控制码00H~1FH(也就是0~31,ASCII码的前32个)冲突,于是乎在每个汉字的区号和位号必须分别加上32(00100000,16进制20H),就得到所谓的国标码,也叫交换码。00110001 00000111
00100000 00100000
———————————————————
01010001 00100111
用十六进制表示为D1A7。
3、
内码
,文本中通常会混合使用中文字符和英文字符,因此有时候无法识别两个字节是两个单独的ASCII字符,还是一个汉字字符。因此,GB2312规定汉字的两个字节的最高位都为1,即在国标码的基础上加上128,这种高位为1的双字节汉字编码即为GB2312汉字的内码。01010001 00100111
10000000 10000000
———————————————————
11010001 10100111
用十六进制表示为D1A7,内码也就是字符用GB2312编码的结果了。来验证一下:
>>> u'学'.encode('gb2312') '\xd1\xa7'
GB2312并未包含繁体字和生僻字,因此在1995年出现了《汉字编码扩展规范》(GBK),GBK完全兼容GB2312,另外还收录了汉字部首符号、竖排标点符号等字符。Unicode3.1出现后,新的中文编码GB18030也随之诞生,GB18030编码向下兼容GBK和GB2312,并收录了所有Unicode3.1中的字符。
字符集和字符编码(Charset & Encoding)
——每个软件开发人员应该无条件掌握的知识!
——Unicode伟大的创想!
相信大家一定碰到过,打开某个网页,却显示一堆像乱码,如”бЇЯАзЪСЯ”、”�????????”?还记得HTTP中的Accept-Charset、DontTrackMeHere、Accept-Language、Content-Encoding、Content-Language等消息头字段?这些就是接下来我们要探讨的。
1.基础知识
计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为”编码”;反之,将存储在计算机中的二进制数解析显示出来,称为”解码”,如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。
字符集(Charset):是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
2.常用字符集和字符编码
常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。
2.1. ASCII字符集&编码
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统(但是有被Unicode追上的迹象),并等同于国际标准ISO\/IEC 646。
ASCII字符集:主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
ASCII编码:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符;但是7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。ASCII字符集映射到数字编码规则如下图所示:
![]()
图1 ASCII编码表
图2 扩展ASCII编码表
ASCII的最大缺点是只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃ASCII而转用Unicode。
2.2. GBXXXX字符集&编码
计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
上述编码规则就是GB2312。GB2312或GB2312-80是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又称GB0,由中国国家标准总局发布,1981年5月1日实施。GB2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。对于人名、古汉语等方面出现的罕用字,GB2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现。下图是GB2312编码的开始部分(由于其非常庞大,只列举开始部分,具体可查看GB2312简体中文编码表):
图3 GB2312编码表的开始部分
由于GB 2312-80只收录6763个汉字,有不少汉字,如部分在GB 2312-80推出以后才简化的汉字(如”啰”),部分人名用字(如中国前总理朱镕基的”镕”字),台湾及香港使用的繁体字,日语及朝鲜语汉字等,并未有收录在内。于是厂商微软利用GB 2312-80未使用的编码空间,收录GB 13000.1-93全部字符制定了GBK编码。根据微软资料,GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展(之前CP936和GB 2312-80一模一样),最早实现于Windows 95简体中文版。虽然GBK收录GB 13000.1-93的全部字符,但编码方式并不相同。GBK自身并非国家标准,只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为”技术规范指导性文件”。原始GB13000一直未被业界采用,后续国家标准GB18030技术上兼容GBK而非GB13000。
GB 18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点:
与UTF-8相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。
编码空间庞大,最多可定义161万个字符。
支持中国国内少数民族的文字,不需要动用造字区。
汉字收录范围包含繁体汉字以及日韩汉字
图4 GB18030编码总体结构
本规格的初版使中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000年3月17日发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005年11月8日发布,2006年5月1日实施。此规格为在中国境内所有软件产品支持的强制规格。
2.3. BIG5字符集&编码
Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字。中文码分为内码及交换码两类,Big5属中文内码,知名的中文交换码有CCCII、CNS11643。Big5虽普及于台湾、香港与澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准。倚天中文系统、Windows等主要系统的字符集都是以Big5为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003年,Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。
Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为”高位字节”,第二个字节称为”低位字节”。”高位字节”使用了0x81-0xFE,”低位字节”使用了0x40-0x7E,及0xA1-0xFE。在Big5的分区中:
0x8140-0xA0FE 保留给用户自定义字符(造字区)
0xA140-0xA3BF 标点符号、希腊字母及特殊符号,包括在0xA259-0xA261,安放了九个计量用汉字:兙兛兞兝兡兣嗧瓩糎。
0xA3C0-0xA3FE 保留。此区没有开放作造字区用。
0xA440-0xC67E 常用汉字,先按笔划再按部首排序。
0xC6A1-0xC8FE 保留给用户自定义字符(造字区)
0xC940-0xF9D5 次常用汉字,亦是先按笔划再按部首排序。
0xF9D6-0xFEFE 保留给用户自定义字符(造字区)
Unicode字符集&UTF编码
3.伟大的创想Unicode
——不得不单独说Unicode
像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB232\/GBK\/GB18030\/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。
为了解决这个问题,一个伟大的创想产生了——Unicode。Unicode编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学(etymological)理由使不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录”模式”了。U+0041总是代表’A’,即使这种语言没有’A’这个字符。
在计算机科学领域中,Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。Unicode 是基于通用字符集(Universal Character Set)的标准来发展,并且同时也以书本的形式[1]对外发表。Unicode 还不断在扩增, 每个新版本插入更多新的字符。直至目前为止的第六版,Unicode 就已经包含了超过十万个字符(在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一)、一组可用以作为视觉参考的代码图表、一套编码方法与一组标准字符编码、一套包含了上标字、下标字等字符特性的枚举等。Unicode 组织(The Unicode Consortium)是由一个非营利性的机构所运作,并主导 Unicode 的后续发展,其目标在于:将既有的字符编码方案以Unicode 编码方案来加以取代,特别是既有的方案在多语环境下,皆仅有有限的空间以及不兼容的问题。
(可以这样理解:Unicode是字符集,UTF-32\/ UTF-16\/ UTF-8是三种字符编码方案。)
3.1.UCS & UNICODE
通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或称ISO\/IEC 10646)标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO\/IEC 10646 项目,后者开发的统一码项目。因此最初制定了不同的标准。
1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO\/IEC JTC1\/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode一般都会采用有关字码最常见的字型,但ISO 10646一般都尽可能采用Century字型。
3.2.UTF-32
上述使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph),每个数字代表唯一的至少在某种语言中使用的符号的编码方案,称为UTF-32。UTF-32又称UCS-4是一种将Unicode字符编码的协定,对每个字符都使用4字节。就空间而言,是非常没有效率的。
这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。
3.3.UTF-16
尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的”星芒层(astral plane)”内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第N个字符,直到它不成立为止这总是一个不错的推断。其编码方法是:
如果字符编码U小于0x10000,也就是十进制的0到65535之内,则直接使用两字节表示;
如果字符编码U大于0x10000,由于UNICODE编码范围最大为0x10FFFF,从0x10000到0x10FFFF之间 共有0xFFFFF个编码,也就是需要20个bit就可以标示这些编码。用U’表示从0-0xFFFFF之间的值,将其前 10 bit作为高位和16 bit的数值0xD800进行 逻辑or 操作,将后10 bit作为低位和0xDC00做 逻辑or 操作,这样组成的 4个byte就构成了U的编码。
对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在UTF-16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。(对于UTF-32编码方式,则有更多种可能的字节排列。)只要文档没有离开你的计算机,它还是安全的——同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节4E 2D表达的到底是U+4E2D还是U+2D4E。
为了解决这个问题,多字节的Unicode编码方式定义了一个”字节顺序标记(Byte Order Mark)”,它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序反向了。
3.4.UTF-8
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节为每个字符编码:
128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
其他极少使用的Unicode辅助平面的字符使用四字节编码。
在处理经常会用到的ASCII字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,(在这一条上你得相信我,因为我不打算给你展示它的数学原理。)由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以utf-8编码的文档在不同的计算机之间是一样的比特流。
总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在UTF-8字符串中字符数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。
优点
UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。
任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。举例说,字符值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。
缺点
因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作 — 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。
4.Accept-Charset\/DontTrackMeHere\/Accept-Language\/Content-Type\/Content-Encoding\/Content-Language
在HTTP中,与字符集和字符编码相关的消息头是Accept-Charset\/Content-Type,另外主区区分Accept-Charset\/DontTrackMeHere\/Accept-Language\/Content-Type\/Content-Encoding\/Content-Language:
Accept-Charset:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案);
DontTrackMeHere:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码);
Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;
Content-Type:WEB服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text\/html; charset=’gb2312′
Content-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。
参考文献&进一步阅读
百度百科. 字符集., 2010-12-28
维基百科. 字符编码., 2011-1-5
维基百科. ASCII., 2011-4-5
维基百科. GB2312., 2011-3-17
维基百科. GB18030., 2010-3-10
维基百科. GBK., 2011-3-7
维基百科. Unicode., 2011-4-30
Laruence. 字符编码详解(基础)., 2009-8-22
Jan Hunt. Character Sets and Encoding for Web Designers – UCS/UNICODE.