弘帝企业智能建站系统交流平台

 找回密码
 立即注册
查看: 2593|回复: 0

彻底搞懂字符编码

[复制链接]
发表于 2021-3-26 15:44:27 | 显示全部楼层 |阅读模式
摘自:https://blog.csdn.net/wm_1991/ar ... 1001.2101.3001.4242

1.ASCII(American Standard Code for Information Interchange)
  美国信息交换标准代码,这是计算机上最早使用的通用的编码方案。那个时候计算机还只是拉丁文字的专利,根本没有想到现在计算机的发展势头,如果想到了,可能一开始就会使用unicode了。当时绝大部分专家都认为,要用计算机,必须熟练掌握英文。这种编码占用7个Bit,在计算机中占用一个字节,8位,最高位没用,通讯的时候有时用作奇偶校验位。因此ASCII编码的取值范围实际上是:0x00-0x7f,只能表示128个字符。后来发现128个不太够用,做了扩展,叫做ASCII扩展编码,用足八位,取值范围变成:0x00-0xff,能表示256个字符。其实这种扩展意义不大,因为256个字符表示一些非拉丁文字远远不够,但是表示拉丁文字,又用不完。所以扩展的意义还是为了下面的ANSI编码服务。

2.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才会出现!

3.MBCS(Multi-Byte Chactacter System(Set))
  多字节字符系统或者字符集,基于ANSI编码的原理上,对一个字符的表示实际上无法确定他需要占用几个字节的,只能从编码本身来区分和解释。因此计算机在存储的时候,就是采用多字节存储的形式。也就是你需要几个字节我给你放几个字节,比如A我给你放一个字节,比如"中“,我就给你放两个字节,这样的字符表示形式就是MBCS。

在基于GBK的windows中,不会超过2个字节,所以windows这种表示形式有叫做DBCS(Double-Byte Chactacter System),其实算是MBCS的一个特例。C语言默认存放字符串就是用的MBCS格式。从原理上来说,这样是非常经济的一种方式。

4.CodePage
  
代码页,最早来自IBM,后来被微软,oracle ,SAP等广泛采用。因为ANSI编码每个国家都不统一,不兼容,可能导致冲突,所以一个系统在处理文字的时候,必须要告诉计算机你的ANSI是哪个国家和地区的标准,这种国家和标准的代号(其实就是字符编码格式的代号),微软称为Codepage代码页,其实这个代码页和字符集编码的意思是一样的。告诉你代码页,本质就是告诉了你编码格式。

但是不同厂家的代码页可能是完全不同,哪怕是同样的编码,比如, UTF-8字符编码 在IBM对应的代码页是1208,在微软对应的是65001,在德国的SAP公司对应的是 4110 。所以啊,其实本来就是一个东西,大家各自为政,搞那么多新名词,实在没必要!所以标准还是很重要的!!!

比如GBK的在微软的代码页是936,告诉你代码页是936其实和告诉你我编码格式是GBK效果完全相同。那么处理文本的时候就不会有问题,不会去考虑某个代码是显示的韩文还是中文,同样,日文和韩文的代码页就和中文不同,这样就可以避免编码冲突导致计算机不知如何处理的问题。当然用这个也可以很容易的切换语言版本。但是这都是治标不治本的方法,还是无法解决同时显示多种语言的问题,所以最后还是都用unicode吧,永远不会有冲突了。

5.Unicode(Universal Code)
  这是一个编码方案,说白了就是一张包含全世界所有文字的一个编码表,不管你用的上,用不上,不管是现在用的,还是以前用过的,只要这个世界上存在的文字符号,统统给你一个唯一的编码,这样就不可能有任何冲突了。不管你要同时显示任何文字,都没有问题。因此在这样的方案下,Unicode出现了。Unicode编码范围是:0-0x10FFFF,可以容纳1114112个字符,100多万啊。全世界的字符根本用不完了,Unicode 5.0版本中,才用了238605个码位。所以足够了。

因此从码位范围看,严格的unicode需要3个字节来存储。但是考虑到理解性和计算机处理的方便性,理论上还是用4个字节来描述。

   Unicode采用的汉字相关编码用的是《CJK统一汉字编码字符集》— 国家标准 GB13000.1 是完全等同于国际标准《通用多八位编码字符集 (UCS)》 ISO 10646.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)。早期的  unicodeUnicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。每个平面有2^16=65536个码位。Unicode计划使用了17个平面,一共有17*65536=1114112个码位。在Unicode 5.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编码时会介绍。如前所述在Unicode 5.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个汉字。



6.Unicode的实现方案
   Unicode其实只是一张巨大的编码表。要在计算机里面实现,也出现了几种不同的方案。也就是说如何表示unicode编码的问题。

(1)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。

(2)UTF-16
UTF-16编码以16位无符号整数为单位。注意是16位为一个单位,不表示一个字符就只有16位。现在机器上的unicode编码一般指的就是UTF-16。绝大部分2个字节就够了,但是不能绝对的说所有字符都是2个字节。这个要看字符的unicode编码处于什么范围而定,有可能是2个字节,也可能是4个字节。这点请注意!

下面算法解释来自百度百科。

我们把Unicode  unicode编码记作U。编码规则如下:
  如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。如果U≥0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。为什么U'可以被写成20个二进制位?Unicode的最大码位是0x10ffff,减去0x10000后,U'的最大值是0xfffff,所以肯定可以用20个二进制位表示。

    例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD8430xDC30。   

    按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。为了将一个WORD的UTF-16编码与两个WORD的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate):   

D800-DB7F ║ High Surrogates ║ 高位替代   
DB80-DBFF ║ High Private Use Surrogates ║ 高位专用替代   
DC00-DFFF ║ Low Surrogates ║ 低位替代   
   高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。那么,高位专用替代是什么意思?我们来解答这个问题,顺便看看怎么由UTF-16编码推导Unicode编码。   

  如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:   1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111   按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到   1110 0000 0000 0000 0000 - 1111 1111 11111111 1111  即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。

(3)UTF-32
这个就简单了,和Unicode码表基本一一对应,固定四个字节。
为什么不采用UTF-32呢,因为unicode定义的范围太大了,其实99%的人使用的字符编码不会超过2个字节,所以如同统一用4个字节,简单倒是简单了,但是数据冗余确实太大了,不好,所以16位是最好的。就算遇到超过16位能表示的字符,我们也可以通过上面讲到的代理技术,采用32位标识,这样的方案是最好的。所以现在绝大部分机器实现unicode还是采用的utf-16的方案。当然也有UTF-8的方案。比如windows用的就是UTF16方案,不少linux用的就是utf8方案。

7. 编码存储差异

这里就要引出两个名词:
LE(little endian):小字节字节序,意思就是一个单元在计算机中的存放时按照低位在前(低地址),高位在后(高地址)的模式存放。

BE(big endian):大字节字节序,和LE相反,是高位在前,低位在后。

比如一个unicode编码为:0x006C49,如果是LE,那么在文件中的存放顺序应该是:49 6c 00如果是BE ,那么顺序应该是:00 6c 49

8.编码格式的检测

到底采用什么编码,如果能检测就好了。专家们也是这么想的,所以专家给每种格式和字节序规定了一些特殊的编码,这些编码在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 ,只要有字库,任何文字都能同时显示,也可以到任何语言的平台上的去运行,不再有乱码的困惑!  其实现在绝大部分软件已经是这么做的了!

   另外也不要被很多名词属于所迷惑,其实这些只是标准的问题,根本没有什么新的东西,更没有什么复杂的东西。

Unicode编码及其实现:UTF-16、UTF-8,and more

本文主要讨论Unicode的编码及其各种实现,着重讨论UTF-16,UTF-8的实现规则,以及Big-endian和Little-Endian的存储顺序。


一、Unicode编码

        Unicode出现之前已经有各种编码标准:ANSI、ISO8859-1、GB2312、GBK以及BIG-5等。Unicode试图统一各种编码,在Unicode演进过程中,也有自身不断修复的过程:刚开始的时候用16位表达65535个字符,认为已经足够收集所有的字符;后来随着大量中文、韩文和日文等表意文字的加入,已经超出了65535个字符,16位已经不能描述所有的字符集了。

        在Unicode字符集中的某个字符对应的代码值,称作代码点(Code Point),用16进制书写,并加上U+前缀。比如,‘田’的代码点是U+7530;‘A’的代码点是U+0041。

        Unicode定义的字符集已经超过16位所能表达的范围,把所有这些CodePoint分成17个代码平面(Code Plane):

U+0000 ~ U+FFFF划入基本多语言平面(Basic MultilingualPlane,简记为BMP);
其余划入16个辅助平面(Supplementary Plane),代码点范围U+10000 ~ U+10FFFF。
        虽然这样划分,但并不是每个Plane中的Code point都对应有字符,这里面有保留的,还有特殊用途的。


二、Unicode编码的实现

        Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的,但是在实际存储和传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。

        对Unicode编码的主要有UTF-16BE、UTF-16LE、UTF-8、UTF-7以及UTF-32等实现方式,目前常用的实现方式是UTF-16LE、UTF-16BE和UTF-8。


2.1 UTF-16
        UTF-16是用16bit编码来表达Unicode,这样表达范围是216(即65536),也就是UTF-16的代码单元(Code Unit)为16bits。如果表达BMP内的字符,用一个UTF-16的Code Unit就可表达,对于辅助平面内的字符,UTF-16有巧妙的设计。

        落在BMP内,从U+D800到U+DFFF之间的Code Point区段是永久保留不映射到字符, UTF-16利用这保留下来的0xD800-0xDFFF区段的CodePoint来对辅助平面内的字符的Code Point进行编码。


对U+0000.. U+D7FF以及U+E000.. U+FFFF的编码

        UTF-16与UCS-2对这个范围内的CodePoint进行编码,采用单个16bit长的CodeUnit,数值等价于对应的Code Point。BMP中的这些Code Point是仅有的可以被UCS-2表示的Code Point。

对U+10000.. U+10FFFF的编码

        辅助平面(Supplementary Planes)中的CodePoint,在UTF-16中被编码为一对16bit长的Code Unit(即32bit,4Bytes),称作代理对(surrogate pair)。


具体方法是:

UTF-16解码

hi \ lo

DC00

DC01

   …   

DFFF

D800

10000

10001

103FF

D801

10400

10401

107FF

  &#8942;

&#8942;

&#8942;

&#8945;

&#8942;

DBFF

10FC00

10FC01

10FFFF


Code Point减去0x10000, 得到的值是长度为20bit(0..0xFFFFF);
步骤1得到数值的高位的10比特的值(值范围为0..0x3FF)被加上0xD800得到第一个Code Unit或称作高位代理(high surrogate)或前导代理(lead surrogate)。取值范围是0xD800..0xDBFF。
步骤1得到数值的低位的10比特的值(值范围为0..0x3FF)被加上0xDC00得到第二个Code Unit或称作低位代理(low surrogate)或后尾代理(trail surrogate)。取值范围是0xDC00..0xDFFF。
        这样,这个范围内的字符就被编码成了一个代理对[lead surrogate,trail surrogate]:两个16bits的Code Unit,取值范围分别是0xD800..0xDBFF和0xDC00..0xDFFF。而BMP中得到的Code Unit的范围是0x0000..0xFFFF(0xD800..0xDFFF是保留的,不包含其中),所以这三个区段是相互不重叠的,在解码时很容易实现。

        UTF-16解码[高位代理+低位代理]得到的Code Unit对与Code Point的对应关系如上表所示。


        下面以对U+64321的UTF-16编码为例,看一下对于辅助平面内的字符是如何编码的:

V  = 0x64321

Vx = V - 0x10000

     = 0x54321

     = 01010100 0011 0010 0001



Vh = 01 0101 0000 // Vx 的高位部份的 10 bits

Vl  = 11 0010 0001 // Vx 的低位部份的 10 bits

w1 = 0xD800           // 结果的前16位元初始值

w2 = 0xDC00          // 结果的后16位元初始值



w1 = w1 | Vh

   = 1101 1000 0000 0000

     |             01 0101 0000

   = 1101 1001 0101 0000

   = 0xD950



w2 = w2 | Vl

   = 1101 1100 0000 0000

    |              11 0010 0001

   = 1101 1111 0010 0001

   = 0xDF21


        所以,这个字 U+64321 最终的 UTF-16 编码是:

0xD950 0xDF21


        UTF-16的Code Unit是16bits,两个字节。存储一个Code Unit的时候,还有存取的先后顺序问题,也就是Endian问题,这在后面章节讲述。


2.2 UTF-8
        UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,使用一至四个字节为每个字符编码:

Unicode范围为U+0000..U+007F 的128个ASCII字符只需一个字节编码;
Unicode范围为U+0080..U+07FF的字符需要二个字节编码;
Unicode范围为U+0800..U+FFFF的其他BMP中的字符(这包含了大部分常用字)使用三个字节编码;
Unicode 辅助平面的字符(其他极少使用的字符)使用四字节编码。
        对上述提及的第四种字符而言,UTF-8使用四个字节来编码似乎太耗费资源了。但UTF-8对所有常用的字符都只用三个字节表达,而且UTF-16编码对前述的第四种字符同样需要四个字节来编码,而如果是ASCII居多的字符,UTF-8能极大的节约存储空间。UTF-8逐渐成为电子邮件、网页及其他储存或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。互联网邮件联盟(IMC)建议所有电子邮件软件都支持UTF-8编码。


        对CodePoint各个范围内的字符进行UTF-8编码的规则如下:

Code point

UTF-8字节流

U+00000000 – U+0000007F

0xxxxxxx

U+00000080 – U+000007FF

110xxxxx 10xxxxxx

U+00000800 – U+0000FFFF

1110xxxx 10xxxxxx 10xxxxxx

U+00010000 – U+001FFFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx


其中,U+D800到U+DFFF之间的区段在Unicode字符集的定义中没有具体字符使用的,被用来在UTF-16编码中对辅助平面的字符进行编码。


        下面以“田”(Code Point为U+7530)为例,看如何对其进行UTF-8编码:

U+7530落在U+0800..U+FFFF区间,采用三字节编码;
0x7530转换为二进制为111 010100 110000;
代入表中,得到111001111001010010110000;
        这样,得到“田”(U+7530)的UTF-8编码:0xE7 94 B0。


        知道UTF-8的编码规则,我们可以对于UTF-8编码中的任意字节B,进行下面解码:

如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码(字符的第一个字节之外的后编码);
如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示。


2.3 UCS-2 vs UTF-16,UCS-4 vs UTF-32
        UCS-2每个字符占用2个字节。UCS-2是UTF-16的子集。在没有辅助平面前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,UTF-16加入了对辅助平面内的字符的支持。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不支持UTF-16中超过2bytes的字集。亦即,对于小于0x10000的UCS码,UTF-16编码就等于UCS码。Java早期版本对Unicode的支持,就只是UCS-2的支持,现在加入了对UTF-16的完整支持。



        UCS-4与UTF-32的意义一致,对每个字符都使用4字节(31位字符集,加上恒为0的首位,共需占据32位)。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。虽然每一个Code Point使用固定长定的字节看似方便,对于普通只需要2个字节存储的常用字占绝大对数的字符集来说,却极大的浪费了空间,并没怎么得到应用。


三、Big-Endian/Little-Endian与BOM
        在讲UTF-16编码方式时说到,UTF-16编码的Code Unit是2个字节,这两个字节在传输和存储过程中,高/低位位置不同,是不同的字符。比如,“田”的UTF-16编码是0x7530,但是如果存成0x3075,就变成了“ふ”,成了另外的字符。

        所以,为了识别一个编码过的字符的存储顺序,必须用特殊字符来指示。Unicode字符中U+FEFF被用来指示这种存储顺序,被称作Byte Order Mark(BOM)。

Big-Endian:最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。
Little Endian:最低位地址存放低位字节,可称低位优先,内存从最低地址开始按顺序存放(低数位数字先写)。最低位字节放最前面。
        BOM在Big-Endian系统上存储为FE FF;而在Big-Endian系统上存储则为FF FE。所以在以Big-Endian存储的UTF-16(UTF-16BE)的文件的开头,用FEFF指示;以Little-Endian存储的UTF-16(UTF-16LE)的文件的开头,用FFFE指示。

        BOM的UTF-8编码为11101111 1011101110111111 (EF BB BF),所以一般EF BB BF被放在文本的开头,用来指示其编码为UTF-8。
        

四、Unicode编码实践



        在Windows的文本编辑工具记事本上,选择“另存为”的时候,用户可以选择不同的编码选项,对应编码选项有“ANSI”,“Unicode”,“Unicode big endian”,以及“UTF-8”。因为Windows的存储方式是Little-Endian,所以“Unicode”,“Unicode big endian”对应的分别是UTF-16LE和UTF-16BE。

      

        读者可以试着编写一串字符,然后分别用不同的编码保存,再用可以16进制编写的纯文本编辑工具(如,Ultra-edit)来检验一下具体的编码实现和存储顺序。下面是笔者将“田海立(U+7530, U+6D77, U+7ACB)”以不同编码方式保存,得到的结果:

田海立_UTF-16BE.txt

        FEFF75306D777ACB

田海立_UTF-16LE.txt

        FFFE3075776DCB7A

田海立_UTF-8.txt

        EFBBBFE794B0E6B5B7E7AB8B

        为了明确起见,BOM的编码用粗体标注;田的编码用红色标注;海的编码用绿色标注;立的编码用蓝色标注。可以看到,记事本(Notepad)存储的Unicode编码的文件的开头位置,用BOM的相应编码指示了编码格式。


【后记】历史

        最近需要用到Unicode的编码实现方式,又收集了一下资料。发现早在06年的时候,笔者就准备总结一下Unicode的编码实现,文档里也已经有了提纲。现在也不记得当时什么原因给耽搁了,好在现在及时总计归纳。好脑子不如烂笔头啊。如果当初总结下来,现在也不用再浪费时间收集资料。

        希望,这次的总结能比较完善,以后再用到Unicode编码,只要参考此文即可!(当然前提是Unicode标准别又演进了^_^)


【附】基本概念对照
Code Point代码点或码位
Code Unit代码单元或码元,是指一个已编码的文本中具有最短的比特组合的单元。对于UTF-8来说,码元是8比特长;对于UTF-16来说,码元是16比特长;对于UTF-32来说,码元是32比特长。
BMP - Basic Multilingual Plane
UTF - Unicode Transformation Format
BOM – Byte Order Mark
UCS - Universal Character Set
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|弘帝企业智能建站系统 ( 皖ICP备07503252号 )

GMT+8, 2024-11-24 00:30 , Processed in 0.053306 second(s), 15 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表