最近有一些朋友常问我一些乱码的问题,和他们交流过程中,发现这个编码的相关知识还真是杂乱不堪, 不少人对一些知识理解似乎也有些偏差,网上百度,google的内容,也有不少以讹传讹, 根本就是错误的(例如说 unicode编码是两个字节),各种软件让你选择编码的时候,常常是很长的一个选单,让用户不知道该如何选。 基于这样的问题,我就写下我的理解吧,一方面帮助一些需要帮助的人纠正认识,一方面作为自己以后备查的资料。
ASCII(American Standard Code for Information Interchange)
美国信息交换标准代码,这是计算机上最早使用的通用的编码方案。
那个时候计算机还只是拉丁文字的专利,根本没有想到现在计算机的发展势头,如果想到了,可能一开始就会使用unicode了。
当时绝大部分专家都认为,要用计算机,必须熟练掌握英文。
这种编码占用7个Bit,在计算机中占用一个字节,8位,最高位没用,通讯的时候有时用作奇偶校验位。
因此ASCII编码的取值范围实际上是:0x00-0x7f
,只能表示128个字符。
后来发现128个不太够用,做了扩展,叫做ASCII扩展编码,用足八位,取值范围变成:0x00-0xff
,能表示256个字符。
其实这种扩展意义不大,因为256个字符表示一些非拉丁文字远远不够,但是表示拉丁文字,又用不完。
所以扩展的意义还是为了下面的ANSI编码服务。
ANSI(American National Standard Institite)
美国国家标准协会,也就是说,每个国家(非拉丁语系国家)自己制定自己的文字的编码规则,并得到了ANSI认可, 符合ANSI的标准,全世界在表示对应国家文字的时候都通用这种编码就叫ANSI编码。 换句话说,中国的ANSI编码和在日本的ANSI的意思是不一样的,因为都代表自己国家的文字编码标准。 比如中国的ANSI对应就是GB2312标准,日本就是JIT标准,香港,台湾对应的是BIG5标准等等。 当然这个问题也比较复杂,微软从95开始,用就是自己搞的一个标准GBK。GB2312里面只有6763个汉字,682个符号,所以确实有时候不是很够用。 GBK一直能和GB2312相互混淆并且相安无事的一个重要原因是GBK全面兼容GB2312,所以没有出现任何冲突, 你用GB2312编码的文件通过GBK去解释一定能获得相同的显示效果,换句话说:GBK对GB2312就是,你有的,我也有,你没得的,我还有!
好了,ANSI的标准是什么呢,首先是ASCII的代码你不能用!也就是说ASCII码在任何ANSI中应该都是相同的。
其他的,你们自己扩展。所以呢,中国人就把ASCII码变成8位,0x7f
之前我不动你的,我从0xa0
开始编,0xa0-0xff
才95个码位,
对于中国字那简直是杯水车薪,因此,就用两个字节吧,因此编码范围就从0xA1A1-0xFEFE
,这个范围可以表示23901个汉字。
基本够用了吧,GB2312才7000多个呢!GBK更猛,编码范围是从0x8140-0xFEFE
,可以表示3万多个汉字。
可以看出,这两种方案,都能保证汉字头一个字节在0x7f以上,从而和ASCII不会发生冲突。能够实现英文和汉字同时显示。
BIG5,香港和台湾用的比较多,繁体,范围:0xA140-0xF9FE
,0xA1A1-0xF9FE
,每个字由两个字节组成,
其第一字节编码范围为0xA1-0xF9
,第二字节编码范围为0x40-0x7E
与0xA1-0xFE
,
总计收入13868个字(包括5401个常用字、7652个次常用字、7个扩充字、以及808个各式符号)。
那么到底ANSI是多少位呢?这个不一定!比如在GB2312和GBK,BIG5中,是两位! 但是其他标准或者其他语言如果不够用,就完全可能不止两位!
例如:GB18030:GB18030-2000(GBK2K)在GBK的基础上进一步扩展了汉字,增加了藏、蒙等少数民族的字形。
GBK2K从根本上解决了字位不够,字形不足的问题。它有几个特点:它并没有确定所有的字形,只是规定了编码范围,
留待以后扩充。编码是变长的,其二字节部分与GBK兼容;四字节部分是扩充的字形、字位,
其编码范围是首字节0x81-0xfe
、二字节0x30-0x39
、三字节0x81-0xfe
、四字节0x30-0x39
。
它的推广是分阶段的,首先要求实现的是能够完全映射到Unicode3.0标准的所有字形。它是国家标准,是强制性的。
搞懂了ANSI的含义,我们发现ANSI有个致命的缺陷,就是每个标准是各自为阵的,不保证能兼容。 换句话说,要同时显示中文和日本文或者阿拉伯文,就完全可能会出现一个编码两个字符集里面都有对应, 不知道该显示哪一个的问题,也就是编码重叠的问题。显然这样的方案不好,所以Unicode才会出现!
MBCS(Multi-Byte Chactacter System(Set))
多字节字符系统或者字符集,基于ANSI编码的原理上,对一个字符的表示实际上无法确定他需要占用几个字节的, 只能从编码本身来区分和解释。因此计算机在存储的时候,就是采用多字节存储的形式。 也就是你需要几个字节我给你放几个字节,比如A我给你放一个字节,比如”中“,我就给你放两个字节, 这样的字符表示形式就是MBCS。在基于GBK的windows中,不会超过2个字节, 所以windows这种表示形式有叫做DBCS(Double-Byte Chactacter System), 其实算是MBCS的一个特例。C语言默认存放字符串就是用的MBCS格式。从原理上来说,这样是非常经济的一种方式。
CodePage
代码页,最早来自IBM,后来被微软,oracle,SAP等广泛采用。因为ANSI编码每个国家都不统一,不兼容, 可能导致冲突,所以一个系统在处理文字的时候,必须要告诉计算机你的ANSI是哪个国家和地区的标准, 这种国家和标准的代号(其实就是字符编码格式的代号),微软称为Codepage代码页, 其实这个代码页和字符集编码的意思是一样的。告诉你代码页,本质就是告诉了你编码格式。 但是不同厂家的代码页可能是完全不同,哪怕是同样的编码,比如,UTF-8字符编码在IBM对应的代码页是1208, 在微软对应的是65001,在德国的SAP公司对应的是4110。 所以啊,其实本来就是一个东西,大家各自为政,搞那么多新名词,实在没必要!所以标准还是很重要的!!!
比如GBK的在微软的代码页是936,告诉你代码页是936其实和告诉你我编码格式是GBK效果完全相同。 那么处理文本的时候就不会有问题,不会去考虑某个代码是显示的韩文还是中文,同样,日文和韩文的代码页就和中文不同, 这样就可以避免编码冲突导致计算机不知如何处理的问题。当然用这个也可以很容易的切换语言版本。 但是这都是治标不治本的方法,还是无法解决同时显示多种语言的问题,所以最后还是都用unicode吧,永远不会有冲突了。
Unicode(Universal Code)
这是一个编码方案,说白了就是一张包含全世界所有文字的一个编码表,不管你用的上,用不上,不管是现在用的, 还是以前用过的,只要这个世界上存在的文字符号,统统给你一个唯一的编码,这样就不可能有任何冲突了。 不管你要同时显示任何文字,都没有问题。
因此在这样的方案下,Unicode出现了。Unicode编码范围是:0-0x10FFFF
,可以容纳1114112个字符,100多万啊。
全世界的字符根本用不完了,Unicode5.0版本中,才用了238605个码位。
所以足够了。因此从码位范围看,严格的unicode需要3个字节来存储。
但是考虑到理解性和计算机处理的方便性,理论上还是用4个字节来描述。
Unicode采用的汉字相关编码用的是《CJK统一汉字编码字符集》—国家标准GB13000.1是完全等同于国际标准《通用多八位编码字符集(UCS)》ISO10646.1。
《GB13000.1》中最重要的也经常被采用的是其双字节形式的基本多文种平面。在这65536个码位的空间中,
定义了几乎所有国家或地区的语言文字和符号。其中从0x4E00-0x9FA5
的连续区域包含了20902个来自中国(包括台湾)、日本、韩国的汉字,
称为CJK(Chinese、Japanese、Korean)汉字。CJK是《GB2312-80》、《BIG5》等字符集的超集。
CJK包含了中国,日本,韩国,越南,香港,也就是CJKVH。这个在UNICODE的Charset chart中可以明显看到。 unicode的相关标准可以从unicode.org上面获得,目前已经进行到了6.0版本。
下面这段描述来自百度百科:
Unicode字符集可以简写为UCS(Unicode Character Set)。早期的Unicode标准有UCS-2、UCS-4的说法。 UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。 每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行(row),每行有256个码位(cell)。 group0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。
每个平面有2^16=65536个码位。Unicode计划使用了17个平面,一共有17*65536=1114112个码位。 在Unicode5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。 其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是
0xF0000-0xFFFFD
和0x100000-0x10FFFD
。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA。
平面0也有一个专用区:
0xE000-0xF8FF
,有6400个码位。平面0的0xD800-0xDFFF
,共2048个码位,是一个被称作代理区(Surrogate)的特殊区域。 代理区的目的用两个UTF-16字符表示BMP以外的字符。在介绍UTF-16编码时会介绍。如前所述在Unicode5.0.0版本中,
238605-65534*2-6400-2408=99089
。 余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,它们对应着Unicode目前定义的99089个字符, 其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。
Unicode的实现方案
Unicode其实只是一张巨大的编码表。要在计算机里面实现,也出现了几种不同的方案。也就是说如何表示unicode编码的问题。
UTF-8(UCS Transformation Format 8bit)
这个方案的意思以8位为单位来标识文字,注意并不是说一个文字用8位标识。他其实是一种MBCS方案,可变字节的。 到底需要几个字节表示一个符号,这个要根据这个符号的unicode编码来决定,最多4个字节。
编码规则如下:
Unicode编码(16进制) | UTF-8字节流(二进制) |
---|---|
000000-00007F | 0xxxxxxx |
000080-0007FF | 110xxxxx 10xxxxxx |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F
之间的字符,UTF-8编码与ASCII编码完全相同。
UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。
Unicode的最大码位0x10FFFF
也只有21位。
例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-16(UCS Transformation Format 16bit)
UTF-16编码以16位无符号整数为单位。注意是16位为一个单位,不表示一个字符就只有16位。 现在机器上的unicode编码一般指的就是UTF-16。绝大部分2个字节就够了,但是不能绝对的说所有字符都是2个字节。 这个要看字符的unicode编码处于什么范围而定,有可能是2个字节,也可能是4个字节。这点请注意!
UTF-32(UCS Transformation Format 32bit)
这个就简单了,和Unicode码表基本一一对应,固定四个字节。
为什么不采用UTF-32呢,因为unicode定义的范围太大了,其实99%的人使用的字符编码不会超过2个字节, 所以如同统一用4个字节,简单倒是简单了,但是数据冗余确实太大了,不好,所以16位是最好的。 就算遇到超过16位能表示的字符,我们也可以通过上面讲到的代理技术,采用32位标识,这样的方案是最好的。 所以现在绝大部分机器实现unicode还是采用的utf-16的方案。当然也有UTF-8的方案。 比如windows用的就是UTF16方案,不少linux用的就是utf8方案。
编码存储差异
这里就要引出两个名词:
LE(little endian):小字节字节序,意思就是一个单元在计算机中的存放时按照低位在前(低地址),高位在后(高地址)的模式存放。
BE(big endian):大字节字节序,和LE相反,是高位在前,低位在后。
比如一个unicode编码为:0x006C49
,如果是LE,那么在文件中的存放顺序应该是:49 6c 00
如果是BE,那么顺序应该是:00 6c 49
。
编码格式的检测
到底采用什么编码,如果能检测就好了。专家们也是这么想的,所以专家给每种格式和字节序规定了一些特殊的编码, 这些编码在unicode中是没有使用的,所以不用担心会冲突。 这个叫做BOM(Byte Order Mark)头。意思是字节序标志头。通过它基本能确定编码格式和字节序。
UTF编码 | Byte Order Mark |
---|---|
UTF-8 | EF BB BF |
UTF-16LE | FF FE |
UTF-16BE | FE FF |
UTF-32LE | FF FE 00 00 |
UTF-32BE | 00 00 FE FF |
所以通过检测文件前面的BOM头,基本能确定编码格式和字节序。 但是这个BOM头只是建议添加,不是强制的,所以不少软件和系统没有添加这个BOM头(所以有些软件格式中有带BOM头和NoBOM头的选择), 这个时候要检测什么格式,就比较麻烦了当然可以检测,但是不能保证100%准确, 只能通过编码范围从概率上来检查,虽然准确度还是比较高,但是不能保证100%。 所以,时常看到检测错误的软件,也不奇怪了。
总结
终于写完了,其实这些问题都是不统一导致的,属于历史问题,所以才会有这些困惑, 这里也呼吁所有的软件开发人员自觉的采用Unicode标准进行文字处理,我相信在不久的将来, 这些困扰都不会存在了,因为所有软件都是unicoded,只要有字库,任何文字都能同时显示, 也可以到任何语言的平台上的去运行,不再有乱码的困惑!
其实现在绝大部分软件已经是这么做的了!
另外也不要被很多名词属于所迷惑,其实这些只是标准的问题,根本没有什么新的东西,更没有什么复杂的东西。