Unicode基础知识、UTF-16及UTF-8编码及解码处理

一、ASCII码

在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000到11111111。上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

ASCII码一共规定了128个字符的编码,比如空格"SPACE"是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

二、Unicode

正如上一节所说,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。

Unicode是为整合全世界的所有语言文字而诞生的。任何文字在Unicode中都对应一个值,这个值称为代码点(code point)。代码点的值通常写成 U+ABCD 的格式。可以查询unicode.org,或者专门的汉字对应表。一些人误以为Unicode只是简单的使用16比特的码字,也就是说每一个字符对应 16比特,总共可以表示65536个字符。这是完全不正确的。在Unicode中,一个字母被映射到一个叫做码点(code point)的东西,这个码点可以看作一个纯粹的逻辑概念。至于码点(code point)如何在内存或磁盘中存储是另外的一个故事了。码点(code point)的形式:U+0639,U+的意思就是"Unicode",后面跟的数字是十六进制的。事实上Unicode可以定义的字符数并没有上限,而且现在已经超过65536了。显然,并不是任何Unicode字符都可以用2个字节来表示了。

例如:Hello

在Unicode中,对应的码点(code point)如下:

U+0048 U+0065 U+006C U+006C U+006F

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

1)U+0000 ~ U+FFFF划入基本多语言平面(Basic MultilingualPlane,简记为BMP);

2)其余划入16个辅助平面(Supplementary Plane),代码点范围U+10000-U+10FFFF。

虽然这样划分,但并不是每个Plane中的Code point都对应有字符,这里面有保留的,还有特殊用途的。

三、Unicode的编码实现方式UTF-16与UTF-8

UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思,从这里我们就可以看的出UTF-16与UTF-8就是Unicode在传输和存储中不同的实现方式而已。就好比Unicode是总结的表,但是这张表只是用来总结,真正传输和存储的时候对应的编码和解码还是存在其他的方法的。

  1. UTF-16编码与解码

UTF-16是用16bit编码来表达Unicode,这样表达范围是216(即65536),也就是UTF-16的代码单元(Code Unit)为16bits。如果表达BMP内的字符,用一个UTF-16的Code Unit就可表达。Unicode字符的码位,需要1个或者2个16位长的码元来表示(两个字节或者四个字节),因此这是一个变长表示。

对Unicode的字符编码表分为两个部分:

对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+D800.. U+DFFF,这个这个字段用作保留的四个字节的编码代理。

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

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

具体的流程为:

1)Code Point减去0x10000, 得到的值是长度为20bit(0..0xFFFFF);

2)步骤1得到数值的高位的10比特的值(值范围为0..0x3FF)被加上0xD800得到第一个Code Unit或称作高位代理(high surrogate)或前导代理(lead surrogate)。取值范围是0xD800..0xDBFF。

3)步骤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是保留的,不包含其中),所以这三个区段是相互不重叠的,在解码时很容易实现。

解码的实现可以通过查询解码表来完成(解码表我这里查找不到,有人 知道可以@我),也可以通过下面的方式来解码:

用W1表示UTF-16Code Unit编写的第一个代码单位

   1、判断是否 0x0000 =< W1 <=0xD7FF 或者是 0xE000 =< W1 <= 0xFFFF,如果满足则是在BMP范围内的编码,直接的出相应的unicode编码就可以

   2、否则判断是否0xD800 =< W1 <= 0xDBFF,取W2为W1后的一个Code Unit(如果不存在则说明编码有错误),判断是否0xDC00 =< W2 <= 0xDFFF,如果满足条件(不满足则编码有错误)

   (( W1 & 0x03FF) << 10) | ( W2 & 0x03FF) + 0x10000便可以得到unicode编码的字符

下面以对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

对而解码来说:

   W1 = 0XD950 = 1101 1001 0101 0000

   W2 = 0xDF21 =  1101 1111 0010 0001

W1不满足条件1,则继续判断条件2

0xD800 =< 0XD950 <= 0xDBFF  并且 0xDC00 =<0xDF21<= 0xDFFF

   w1 = (W1 & 0x03FF)<<10 = 0101 0100 0000 0000 0000

   w2 = ( W2 & 0x03FF)= 11 0010 0001

   u = (w1 |  w2) =0101 0100 0011 0010 0001

   u + 0x10000=0x64321

 2.UTF-8的编码与解码

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

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

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

U+7530落在U+0800..U+FFFF区间,采用三字节编码;

0x7530转换为二进制为111 010100 110000;

代入表中,得到111001111001010010110000(xxxxxx为要填入地方);

这样,得到“田”(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字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示。

主要特点

1)向下兼容 ASCII;
2)传输一些文件时占用空间更小,比如 HTML 文件中很多都是字母和符号,用 UTF-8 一个字节就能表示,UTF-16 则不然;
3)实际上 UTF-16 也是变长编码,但是许多程序甚至语言(比如 JavaScript)中都没有提供支持,导致其能表示的字符数量大打折扣,而UTF-8 是很显然的变长编码,几乎在所有的实现中都不存在问题。

对于美国人的使用特点来说用UTF-8就可以满足他们的使用需求了,但是对于亚洲地区的使用者来世是不够的。但UTF-8对所有常用的字符都只用三个字节表达,而且UTF-16编码对前述的第四种字符同样需要四个字节来编码,而如果是ASCII居多的字符,UTF-8能极大的节约存储空间。UTF-8逐渐成为电子邮件、网页及其他储存或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。互联网邮件联盟(IMC)建议所有电子邮件软件都支持UTF-8编码。

参考文献:

1)http://blog.csdn.net/thl789/article/details/7506133

2)http://blog.chinaunix.net/uid-29732842-id-4951677.html

 

posted @ 2015-05-29 17:31  Nancy1  阅读(1168)  评论(0)    收藏  举报