Unicode编码:UTF-8和UTF-16

Unicode编码系统分为编码和实现两个层次。Unicode为每个字符分配了唯一的数字,并且与平台和语言无关,这是Unicode的编码层次。每个Unicode编码后的字符,在储存,网络数字交换时,根据它的实现方式不同而不同,这就是Unicode的实现层次。

Unicode虽然为每个字符分配了唯一的编码(数字),但字符最终要被储存起来,或传输,甚至和其它软件交换数据,这就需要Unicode实现方式来处理关于具体平台和语言的问题。以一个简单例子来说明Unicode实现的重要性。

假设置有个文本文档,它只有"ABCD"这四个字符,那它如何储存在硬盘上呢?字符A,B,C和D在Unicode中的编码分别是:U+0041, U+0042, U+0043和U+0044。如果把他们直接存放在硬盘上,那么它的数据序列如下所示:

+--+--+--+--+--+--+--+--+
|00|41|00|42|00|43|00|44|
+--+--+--+--+--+--+--+--+

显然上面是大头机器储存的结果,但问题也跟着来了,如果把该文件发送给一个小头机器,那么出现什么情况呢?
当然,小头机器收到上述的文件后,经过“按步就班”的方法分析,得结论是:文件包含的内容为:
U+4100, U+4200, U+4300, U+4400

由于没有Unicode实现来支持,文件在传输过程中出现错误。

从这个列子显然可以看到两个问题:
机器平台差别而引起数据出错,大小头机器差异问题与机器平台相关,Unicode实现方式需要解决。
无论是计算语言,还是 web中的HTML和xml,语言中的标识符都可以英语为 基础,如果把英文字符都用两个字节来储存,显然会带来很大开销。

为了辟免数字在储存和传输中的丢失和歪曲问题,Unicode提出了Unicode转换格式(Unicode Translation Format)概念来统称Unicode的所有实现方式。Unicode目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32,其中UTF-8和UTF-16是比较常用的方式,其它实现方式有些仅在一定的国家和地区使用,有些则属于未来的规划方式。

本文主要介绍UTF-8和UTF-16实现方式,其它实方式不作介绍,请参阅相关网站。




UTF-8
UTF-8是Unicode所有实现方式中唯一能与ASCII码兼容的编码方式,并且是一种变长编码,它根据字符codepoint的值不同,采用1到4个变长的字节来编码该字符。UTF-8的编码单元为单个字节,因些UTF-8不存到大头小头机器差异问题,它由Ken Thompson于1992年创建,现在已经标准化为RFC 3629。注意不要把UTF-8写成诸如”utf8”,”UTF8”这样的别名,正确的写法应该是“UTF-8”,虽然有些软件里可以写为utf8,但在Unicode标准里面,只有UTF-8才是它正确的称谓。




下表为codepoint到UTF-8编码的转换规则表。




Codepoint范围 | UTF-8 字节序列

(十六进制) | (二进制)

--------------------------+---------------------------------------------

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx




其实单从上表就可看到它的编码规则了,RFC 3629给它了该编码规则的变换过程,该过程可用于你的代码中。

1)任何一个Codepoint,根据上表中左边各行的范围,可以知道UTF-8编码后的字节数,应准备相应的字节序列。

2)同样,根据上表把字节序列中各个字节中的相应高位置为1或0,其它位暂时设为填充位。

3)把Codepoint写成二制数,然后把该二进制数的最低位填充到上该字节序列的最低填充位上;再把二制位数的次低位填充到该字节序列的次低填充位上;依次进行,直到所有的填充位都完成。

终上述3步得到的结果就是该字符的UTF-8编码。以一个中文例子以说明它的转换规则。

以“中文”二字为例,通过上面的规则,窥探它在UTF-8里面会编码成什么样的字节序列(或字节串)。

“中文的”Unicode串为<U+4e2d,U+6587>,这两个字符都满左边第三行的条件,根据上表,可以得到下面的结果。

U+4e2d = 0100 1110 0010 1101

= 0100 1110 0010 1101

变换成

=>11100100 10111000 10101101

= e4 b8 ad


U+6587 = 0110 0101 1000 0111

= 0110 0101 1000 0111

变换成

=>11100110 10010110 10000111

= e6 96 87

显然,“中文”二字在UTF-8的编码下,它变了成<e4 b8 ad e6 96 87>字节序列。

从U+D800到U+DFFF的codepoint在Unicode是都是不可分配的,因此它不代表字符,故在UTF-8编码里面,不能把这些codepoint进行编码,这需要切记,至于为什么该段的codepoint不可分配,稍后介绍UTF-16后会略知一二。仔细分析编码规则,会发现UTF-8编码具有下面的特点。

与ASCII码兼容:任何ASCII在Unicode中,它的codepoint在[U+0000, U+007F]里,根据上面的规则,它编码后的UTF-8编只有一个字节,并且与它的ASCII值相等。即一个ASCII串也是一个合法的UTF-8串。

长度可知性:经UTF-8编码后的字节序列中第一个字节可以决定该字节列节的字节个数。如果第一个字节以0开始,则表明个数为1,若以110开始则表明个数为2,1110和11110分别表明个数为3和4。

字节值为C0, C1, F5~FF不会出现在UTF-8编码结果里。

给定任何一个UTF-8字节列序串(多个字符对应的UTF-8字节序列组成的串),很容易找到各个字符的边界。

Unicode中任何一个可分配的codepoint,经UTF-8编码后,都对应唯一的字节列序。




2. UTF-16

UTF-8编码采用8位即一个字节作为编码单位,任何字符经UTF-8编码后,得到一个变长的字节列序就是该字符的UTF-8编码。

类似地,UTF-16编码采用16位即双字节作为编码单位,它同样是变长的编码规则;对于BMP中的字符,编码后为两个字节,其它辅助平面的字符,经UTF-16编码后为4个字节。


它的编码规则如下:

1)codepoint值小于0x10000的字符,用codepoint等值的16整数来表示。

2)codepoint值介于0x10000和0x10FFFF之间的,用一个值介于0xD800和0xDBFF的16位整数,以及一个值介于0xDC00和0xDFFFF的16位整数来表示。

3)其它非Unicode字符,包括不可以分配codepoint,不能按照UTF-16进行编码。

注:0xD800到0xDFFF间的值是为UTF-16预留的,所以Unicode把它作为不可以分配字符(非字符codepoint)。

该编码规则可以下面的程序语言来描述

假设U是给字符的codepoint,小于0x10FFFF。




1)如果U < 0x10000,U的编码就是无符号的十六位整数,值和其本身的值一样,处理结

束。

2)如果U等于或者小于0x10FFFF,则设U' = U - 0x10000。此时,U'一定小于或者等于

0xFFFFF,也就是说,U'的值不会超过20位。

3)分别初始化2个16位无符号的整数,W1和W2为0xD800和0xDC00。每个整数都有

10位可以用来对字符进行编码,正好能容纳U'的20位。

4)将U'的高10位分配给W1的低10位,将U'的低10位分配给W2的低10位,处理结

束。




用数字来表示,第2步到第4步如下所示:

U' = yyyyyyyyyyxxxxxxxxxx

W1 = 110110yyyyyyyyyy

W2 = 110111xxxxxxxxxx




3. 标识UTF-16文字和BOM

UTF-8编码单位为字节,并且多节字的编码也规定了他们的顺序(低字节和高字节),因此在储存和传输过程不会出现问题;但UTF-16就不同,它以16位整数、双字节作为编码单位,规定了多个(两个)双字节编码的顺序(高位双字节和低位双字节),但对于每个双字节在储存和传输中,这两个字节谁先谁后的问题。如果不作相关规定,在小头机器中,把会双字节中的低字节储存(或传输)在前面,高字节在后;大头机器则相反,会造成混乱和错误。

为此,Unicode规定UTF-16可根据双字节在实际储存(或传输)的顺序把它标识为UTF-16BE,UTF-16LE或UTF-16。BE和LE分别表示big-endian和little-endian。并确定UTF-16BE文字中,它双字节的储存(或传输)顺序必须是big-endian顺序;同样UTF-16LE文字中,它双字节的储存(或传输)顺序必须是little-endian顺序。标识UTF-16的文字,可能是UTF-16BE,也可能是UTF-16LE文字,这取决于一个称为BOM的标记(或特殊字符,并不是字节)。

Unicode编码系统把U+FEFF作为不可分配字符,并有一个特别的称谓“零宽序不换行的空格”,同时也可作“字节顺序标记”(Byte ORDER Mark, 简称BOM)。这意识着它可以用在那些编码单位大于一个字节的编码方案中(UTF-16和UTF-32),来表示储存(或传输)该编码单元时,它的内部节字顺序。U+FEFF经UTF-16编码结果同样是0xFEFF,可以把该结果插在任何UTF-16文字流或文本。如果该UTF-16文字采用BE的标识方法,那么要把0xFEFF以BE的字节顺序放到UTF-16文字的字节流前面,即把0xFE 0xFF加在该字节流前面;若是标识为LE,则把0xFF 0xFE加在UTF-16字节流前面。

值得注意的一点是,如果字符U+FEFF出任是字节流中的其它地方,而是首字节的话,那它就必须解释为“零宽度不换行的空格”,零宽序空格则表明它的长度为0,不换行则表明它不起换行的作用,就像它不出现一样。标识为UTF-16BE的文字必须按照big-endian的顺序来组装它的双字节,并且不应该给文字前加上BOM;同样地,UTF-16LE的文字必须按照little-endian的顺序来组装它的双字节,并且不应该给文字前加上BOM。如果UTF-16文字是以little-endian顺序储存(或传输)的,那么把该文字标识为UTF-16,并且以它的BOM开头,即在字节流前加上0xFF 0xFE;如果UTF-16文字是以big-endian顺序储存(或传输)的,那么应该把该文字标识为UTF-16,但不强制要求在开头加上BOM(即0xFE 0xFF)。如果文档格式或协议格式要求必须提供BOM的话,无论该文字被标识为UTF-16LE,还是UTF-16BE,一律在文字前面加上相应的BOM,并把它标识为UTF-16。

对于UTF-8,显然是不需要BOM,因为UTF-8的编码单元为字节,根据不是用处理字节顺序问题,无论是little-endian还是big-endian机器,UTF-8的编码结果都是一样的。但为了与UTF-16和UTF-32保持一致,可以在UTF-8中使用BOM,语义与在UTF-16相同。需要注意的是,U+FEFF经UTF-8编码后为字节序列 0xEF 0xBB 0xBF。在window平台下的一些文本程序会在文件内空前根据编码自动加上BOM来标记该文件的编码方式,在一些不支持BOM规则的解释器中会把它显示成空行或换行符,当然可以通过设置让文件取消BOM;Linux平台下的vim则不会。

举例来说,让我们假定有一个字符代表埃及语中描写神Ra的象形文字,它的值是

0x12345。(这个字符在现有的Unicode中并不存在)。




在示例中所有的词组中:




*=Ra




这里"*"代表Ra象形文字(0x12345)。




标识为UTF-16BE,并且不带有BOM的文字:

D8 08 DF 45 00 3D 00 52 00 61




标识为UTF-16LE,并且不带有BOM的文字:

08 D8 45 DF 3D 00 52 00 61 00




采用Big-endian的文字,并且带有BOM:

FE FF D8 08 DF 45 00 3D 00 52 00 61




采用Little-endian的文字,并且带有BOM:

FF FE 08 D8 45 DF 3D 00 52 00 61 00
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值