曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3462|回复: 0
打印 上一主题 下一主题

Unicode,UCS 和 UTF (BMP, BOM)

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
跳转到指定楼层
楼主
发表于 2011-6-13 14:11:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Unicode 是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode 用数字 0-0x10FFFF 来映射这些字符,最多可以容纳 1114112 个字符,或者说有 1114112 个码位。码位就是可以分配给字符的数字。

Unicode 的学名是 "Universal Multiple-Octet Coded Character Set",简称为 UCS。UCS 可以看作是 "Unicode Character Set" 的缩写。

UCS 规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由 UTF(UCS Transformation Format) 规范规定的,常见的 UTF 规范包括UTF-8、UTF-7、UTF-16。UTF-8、UTF-16、UTF-32 都是将数字转换到程序数据的编码方案。

关于 UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。
UCS-2 就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。所以,UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

下面介绍 USC-4 是如何编码的(从低到高,依次将这 4 个字节称为 第1个字节,第2个字节,第3个字节,第4个字节):

1. 由于 USC-4 中第 4 个字节的最高位必须为 0,所以只有地 7 位可用,可将这 7 位共分成 2^7 = 127 个组 (group) 。

2. 接着将 第3个字节 分为 2^8 = 256 个平面 (plane) 。

3. 再接着将 第2个字节 分为 256 行 (row) 。

4. 最后将 第1个字节 分为 256 个单元 (cell) 。

综合起来说就是,一个组中包含有 256 个平面,每个平面又可以包含 256 个行,每个行中又可以存放 256 个单元。从这里可以知道,每个平面共有 256*256 = 65536 个码位。

在 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 介绍)。

这时,我们会涉及一个概念:BMP
BMP 是 Basic Multilingual Plane 的缩写。它是这么定义的: group 0 的 plane 0 被称为 BMP。也就是说,UCS-4中,当高两个字节(第3 和 第4 字节)为 0 的码位被称作BMP。

有了 BMP,就有了 USC-2 的概念:
将 UCS-4 的BMP 去掉前面的两个零字节就得到了 UCS-2。在 UCS-2 的两个字节前加上两个零字节,就得到了UCS-4的BMP。

下面看 UTF 编码的概念:

UTF-8 就是以 8 位为单元对UCS进行编码。从 UCS-2 到 UTF-8 的编码方式如下:
UCS-2编码(16进制)UTF-8 字节流(二进制)
0000 - 007F0xxxxxxx
0080 - 07FF110xxxxx 10xxxxxx
0800 - FFFF1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
上面表格中,第 1 列表示 Unicode 的码位范围;第 2 列表示将 Unicode 码转换为 UTF-8 字节流的模板:

例1:
例如“汉”字的 Unicode 编码是 6C49。6C49 在 0800-FFFF 之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将 6C49 写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

例2:
Unicode 编码 0x20C30 在0 x010000-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。

如上表所示, Unicode  字符由 2 个字节表示,则编码成 UTF-8 很可能需要 3 个字节(如模板 3 中的情况),而如果UNICODE字符由4个字节表示,则编码成UTF-8 可能需要 6 个字节。用 4 个或 6 个字节去编码一个 Unicode 字符可能太多了,但很少会遇到那样的 Unicode 字符。


UTF-16 以 16 位为单元对UCS进行编码。对于小于 0x10000 的 UCS 码,UTF-16 编码就等于 UCS 码对应的 16 位无符号整数。对于不小于 0x10000 的UCS 码,定义了一个算法。不过由于实际使用的 UCS2,或者 UCS4 的 BMP 必然小于 0x10000,所以就目前而言,可以认为 UTF-16 和 UCS-2 基本相同。但 UCS-2 只是一个编码方案,UTF-16 却要用于实际的传输,所以就不得不考虑字节序的问题。

UTF 的字节序和 BOM
UTF-8 以字节为编码单元,没有字节序的问题。

UTF-16 以两个字节为编码单元,在解释一个 UTF-16 文本前,首先要弄清楚每个编码单元的字节序。例如收到一个 “奎” 的 Unicode 编码是 594E,“乙” 的Unicode 编码是 4E59。如果我们收到 UTF-16 字节流 “594E”,那么这是“奎”还是“乙”?

Unicode 规范中推荐的标记字节顺序的方法是 BOM。BOM 的意思是 Byte Order Mark。

在UCS编码中有一个叫做 "ZERO WIDTH NO-BREAK SPACE" 的字符,它的编码是 FEFF。而 FFFE 在 UCS中 是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 "ZERO WIDTH NO-BREAK SPACE" 。

这样如果接收者收到 FEFF,就表明这个字节流是大端格式 (Big-Endian) 的;如果收到 FFFE,就表明这个字节流是小端格式 (Little-Endian) 的。因此字符 "ZERO WIDTH NO-BREAK SPACE" 又被称作 BOM。

UTF-8 不需要 BOM 来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的 UTF-8 编码是 EF BB BF(可用上面 UTF-8 转换模板进行转换验证)。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是UTF-8编码了。

Windows就是使用 BOM 来标记文本文件的编码方式的。



UTF-16
UTF-16编码以16位无符号整数为单位。我们把 Unicode 编码记作 U。编码规则如下:

如果 U<0x10000,U 的 UTF-16 编码就是 U 对应的16位无符号整数(为书写简便,下文将 16 位无符号整数记作 WORD)。

如果 U≥0x10000,我们先计算 U'=U-0x10000,然后将 U' 写成二进制形式:yyyy yyyy yyxx xxxx xxxx (一共有 20 位),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,即 0xD843 0xDC30。

按照上述规则,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),也就是第 2 个 WORD 不会落在这个范围中,而这个范围又分为两部分,一部分为“高位替代”区,另一部分为“高位专用替代”区,如下所示:
     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 1111 1111 1111 即 0xe0000-0xfffff ,按照编码的相反步骤再加上 0x10000,得到 0xf0000-0x10ffff。这就是UTF-16编码的第一个 WORD 在 0xdb80 到 0xdbff 之间的 Unicode 编码范围,即平面15 和 平面16。因为 Unicode 标准将 平面15 和 平面16 都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-6-20 02:07 , Processed in 0.079267 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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