0、序
初入编程世界的时候,杨公子就经常遇到字符编码的困扰,后来在修炼中不断遇到来自字符编码的骚扰。
杨公子发现,不论是在C语言帮、Java帮、Scala帮还是python帮;不论是做前端、后端、测试还是大数据;不论你用的武器是mysql、oracle、mongodb还是hive、hbase......总之,只要你是编程世界的一名程序员,就一定绕不开来自字符编码这个神秘组织的困扰。
渐渐的杨公子了解到,字符集其实是一个庞大的江湖,它们的故事杨公子这就为你娓娓道来。
1、初入江湖,先识人
在深入了解字符集的江湖故事之前,我们先来了解一下字符编码组织的基本常识。我们经常说字符集、字符编码、排序规则,乱码这些都是什么意思呢?
- 字符集(charset):顾名思义是“许多字符的集合”,这些字符组成一套符号系统,可以组合起来形象的表达各种含义。
- 字符编码(encoding):在计算机中是指一种从二进制编码到某类字符符号的映射规则。
- 排序规则(collation):也称“校对”,是指一组用于某个字符集的比较规则。
- 乱码(messy code):乱码是指使用错误的编码规则解析实际的机器代码生成一些特殊字符的过程。
“字符集”是一种形象表意的工具,“字符编码”是表示字符的一种方式。在计算机出现之前就已经有了这两种技术。计算机中,是使用二进制的方式对字符集重新编码。
在计算机大陆中,要炼成一种“字符编码模型”,需要四步修炼。
要有一个字符库,确定这些字符足够表意。比如ASCII字符集,已经足够表示英语,但不能表示中文,于是产生GB2312字符集。
第一层编码,给每个字符编个数字,英文叫Code Point 或 Code Position。比如ASCII字符集中,65表示“A”,97表示“a”。
第二层编码,确定表示字符的二进制位数(8位,16位,32位)。ASCII使用7位,DBCS(双字节字符集)使用16位。
第三层编码,确定字符二进制值的存储格式(大端法,小端法)。比如X86机器使用小端法。
一种字符集一般只有一种编码方式,当字符集不够用时,会增加一些新的符号,形成新的字符集。对于新的符号会有新的数字,新的编码格式。所以有时“字符集”和“字符编码”的概念并不严格区分。
比如ASCII码,可以指128个字符的字符集,也可以指对这128个字符的编码方式。不过有的字符集有多种编码格式,比如Unicode字符集,Utf8、Utf16都是其编码格式(第二层编码)。
2、字符编码“组织”的诞生与ASCLL
谈到字符编码的诞生,那就必须说一下计算机的历史。计算机最早是在美国诞生,刚开始诞生的时候,只有位的概念,晶体管的高位与低位分别用1和0表示。
但随着计算机的发展,需要表示字符,为了表示更多的字符,开始产生编码。最初的编码是4位的BCD编码,6位的BCD编码(BCDIC),接着产生了至今仍在广泛使用的7位ASCII。
ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。
ASCII由7位表示数字、字母、特殊的控制字符,但是还有其他字符需要加入,于是再加1位,总共8位表示字符。从此,确定了,使用8位是一个字符存储单位,于是诞生了1字节=8位。字节从原始的意思,就是指一个完整的字符。
然而标准ASCII仅仅够美国使用,其他欧洲语言无法加入,于是开始使用最高位进行扩展,扩展后的ASCII,后128位(128255)称为扩展字符,前128位(0127)不变。然而如何扩展字符,产生了不同意见。于是诞生了ISO8859标准,ISO8859制定了多个字符集与多个编码,如下所示:
ISO 8859-1 (Latin-1) - 西欧语言
ISO 8859-2 (Latin-2) - 中欧语言
ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
ISO 8859-4 (Latin-4) - 北欧语言
ISO 8859-5 (Cyrillic) - 斯拉夫语言
ISO 8859-6 (Arabic) - 阿拉伯语
ISO 8859-7 (Greek) - 希腊语
ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)
ISO 8859-8-I - 希伯来语(逻辑顺序)
ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。
ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族
ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族
ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。
ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
扩展后的ASCII最为流行的是ISO 8859-1,同样可以称为Latin-1。
3、中国的字符编码GB系列
在世界各国都在发展字符编码标准的同时,随着计算机的普及与中国的发展,中国也开始有了自己的计算机,自然而然地要使用我们的汉汉字,于是创建自己国家的字符集,并进行编码。
然而1个字节已经无法满足汉字的编码需求,于是使用两个字节表示中文字符,创建了GB2312编码。GB2312在兼容了标准ASCII的基础上,GB2312收录了简化汉字、符号、字母、日文假名等共计7445个字符。GB2312的编码范范是0x2121-0x777E,与ASCII有重叠,通常方法是将GB码的两个字节的最高位置1区别。
GB2312的出现,基本满足了汉字的计算机处理需要,但是奈何我中华文化博大精深,除了常见的简体字之外,我们还有不少罕见的繁体字、甚至是长相怪异的生僻字,这些罕用字GB2312不能处理。
于是产生了GBK编码(是的,就是最常困扰我们的那个GBK)。GBK即汉字内码扩展规范,K为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母。英文全称Chinese Internal Code Specification。GBK共收入21886个汉字和图形符号,同样以双字节表示。
虽然GBK以及满足大部分汉字的要求,但是我们中华名族是五十六个名族团结友爱的大家庭,少数名族的字符一个也不能少,这样的话2个字节就不够用了,于是产生新的国家标准,产生了GB18030。
GB18030编码采用多自己编码,是现在推荐使用的编码,汉字收录范围包含繁体汉字以及日韩汉字。它是一二四字节变长编码,其中包括:
-
单字节,其值从0到0x7F,与 ASCII 编码兼容。
-
双字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x40到0xFE(不包括0x7F),与 GBK标准基本兼容。
-
四字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节从0x81到0xFE,第四个字节从0x30到0x39。
4、Unicode一统天下
4.1 Unicode的诞生
随着计算机传入各国,每个国家开始有自己的不同字符编码,比如台湾编码为BIG5。不同国家之间于是存在着重叠,无法支持多语言办公,不方便进行各国进行交流,于是出现了Unicode。
Unicode起源于1987年,施乐的 Joe Becker、苹果的 Lee Collins 和 Mark Davis 就开始研究能否创造一种全球通用的字符集。
Unicode起源于1987年,施乐的 Joe Becker、苹果的 Lee Collins 和 Mark Davis 就开始研究能否创造一种全球通用的字符集。1988年,Joe Becker 发布了一个草案,提出了“Unicode”的概念,他解释说“‘Unicode’是一种唯一的、统一的、全球的编码”。后来,RLG、Sun、Microsoft、NeXT(乔布斯被赶出苹果后创建的公司)的人也都逐渐加入到Unicode工作组里。1991年1月3日,Unicode联盟组织成立,同年发布了Unicode1.0。同时,ISO组织也在做同样的事情,创造一个全球统一的字符集(Universal Coded Character Set,简称UCS),1993年发布了标准ISO 10646-1。
后来,两个组织认识到,世界不需要两个不兼容的字符集,于是,开始合作。从Unicode2.0开始,开始采用和UCS相同的字库和字码。这样,两个项目仍都存在,并独立地公布各自的标准。但双方都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。所以,现在说到UCS字符集,跟Unicode可以看成一回事。
Unicode编码包含两个层次:
-
第一层定义字符的数值:Unicode用数字 0x0~0x10FFFF 表示所有字符,所以最多可以容纳 1114112 个字符。
-
第二层定义数值的实现方式:数值的编码方式,也就是实现方式。包括 UTF-8,UTF-16,UTF-32 三种。
看到这里有同学会问,Unicode不是两个字节表示字符的码?为什么数值可以到0x10FFFF,这不21位,两个半字节还多了吗?其实,这是混淆了Unicode的数值定义和实现,这根本就是两个概念,Unicode到底用几个字节表示,取决于其实现方式是UTF-8,UTF-16,还是UTF-32。
4.2 UTF-8
UTF,全称“Unicode Transformation Formats”。是Unicode的编码格式。
UTF-8是使用8-bit为单位,对Unicode进行编码的。特点是,对不同范围的字符使用不同长度的编码。
Unicode编码(十六进制) | UTF-8 字节流(二进制) |
---|---|
00000000 | 0000007F 0xxxxxxx |
00000080 | 000007FF 110xxxxx 10xxxxxx |
00000800 | 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx |
00010000 | 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
00200000 | 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
04000000 | 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8编码的最大长度是6个字节。
对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同,用1个字节表示,首位为0。对于0x80-0x7FF之间的字符,用2个字节表示,第一个字节前三位“110”为标志位,第二个字节前两位“10”为标志位。剩下的11位用来表示Unicode值(7FF最多11位)。
同样,UTF-8的3个字节,可以表示0x800-0xFFFF的Unicode(最多16位)。UTF-8的4个字节,可以表示0x10000-0x001FFFFF的Unicode(最多21位)。
4个字节以内,就已经包含了Unicode所有字符。
5、6个字节表示的已经不是Unicode编码范围了,属于UCS-4 编码。早期UTF-8规范也可以达到6字节序列,不过在2003年11月UTF-8 被 RFC 3629 重新规范,只能使用原来Unicode定义的区域, U+0000到U+10FFFF。根据规范,这些字节值将无法出现在合法 UTF-8序列中。
所以我们目前使用的UTF-8编码就是8-bit为单位,四字节的编码格式。
下面杨公子用两个例子来为大家演示Unicode编码如何转换为UTF-8编码:
例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
UTF-8有两个好处:
-
- 1字节字符、2字节字符、3字节字符……的首字节标志位不同,这样可以很清楚的区分一个字节属于1字节字符还是2字节字符,如果一个字节流传输中出现错误,也不会错位,只影响部分字符,根据标志位,很容易找到下个正确字符。mysql的utf8编码就是三字节的。
-
- 兼容ASCII码,英美字符用UTF-8可以一个字节表示。所以,www组织选用UTF-8作为推荐编码格式。2007年,在互联网上,UTF-8格式已经超过了ASCII码。
5、回顾统一简史
当我们回首字符集的发展历程,总结一下大致是这样的。
由于计算机是美国发明的,所以首先是单字节字符集的出现。
最初美国ANSI发明了自己的编码ASCII,7-bit足够,这是标准ASCII。 标准ASCII码没有西欧国家拉丁文、英镑等字符,各公司、国家开始扩展,形成自己的扩展ASCII码字符集,各方混战,不过8-bit也就足够。
天下分久必合,ISO统一了8-bit字符集,叫做ISO 8859。
然后随着计算机的不断发展,开始出现多字节字符集:
由于亚洲国家字符更多,一个字节远远不够,于是用多个字节表示,扩展形成本国字符集,中国GB系列,台湾Big5,日本JIS……,这些叫做多字节字符集(MBCS),windows中用双字节表示,也叫做(DBCS)。
这些字符当时都是群雄割据,各自为政,windows为了迎合大家需求,在哪个国家,默认编码就用那个国家的,不过后来不知怎么被误传为ANSI编码。其实ANSI怎么可能定义世界各国编码,不过可以理解成各编码都是在ANSI基础上扩展的,因为都兼容ANSI的标准ASCII码。
正当字符集的世界混乱不堪之际,ISO再次出手,和Unicode联盟携手打造了Unicode(UCS),意图一统江湖。Unicode确实包罗万象,涵盖了各国字符,于是流行世界。Unicode自身只定义了每个字符的数值,真正二进制编码格式却是UTF-8,UTF-16(UCS-2),UTF-32(UCS-4)。
随着Unicode的不断普及,字符集的天下归于一统,程序员的太平盛世到来了!
参考文档:
【1】百度百科,《盲文》,baike.baidu.com/view/23057.…
【2】百度百科,《SOS》,baike.baidu.com/subview/174…
【3】松田行正,《零ZERO:世界符号大全》,中央编译出版社
【4】Peter Constable,《Character set encoding basics》,SIL,2001-06-13:scripts.sil.org/cms/scripts…
【5】维基百科,《ASCII》,en.wikipedia.org/wiki/ASCII
【6】维基百科,《Extended ASCII》,en.wikipedia.org/wiki/Extend…
【7】百度百科,《ISO-8859》,baike.baidu.com/view/975310…
【8】百度百科,《GBK字库》,baike.baidu.com/view/931619…
【9】百度百科,《gb18030》,baike.baidu.com/view/889058…
【10】Crifan Li,《字符编码详解》,www.crifan.com/files/doc/d…
【11】MSDN,《Code Pages》,msdn.microsoft.com/en-us/libra…
【12】维基百科,《Unicode》en.wikipedia.org/wiki/Unicod…
欢迎关注杨公子的公众号:Coder杨公子