看到题目就知道是涉及到Uncode的,Unicode的编码方式有UTF-32,UTF-16,UTF-8三种,这里为什么单单只讲UTF-16呢?无它,UTF-16这位老兄故事多啊~
在Unicode诞生之前,世界上许多字符集并存而且互不兼容,这导致很多软件在国际化过程中困难重重,随着互联网的普及和国际间交流的增多,问题就愈发严峻了。Unicode正是在这样的大环境下成长起来的。我们可以认为,Unicode的出现,就像秦始皇统一六国,在字符集中实现了统一的“度量衡”,在各个领域中发挥着重要的作用。
从90年代初开始,Unicode一直不断地发展和完善。在Unicode的最初版本中,Unicode是被设计成定长的,长度为16 bits,2 Bytes。这在形式上跟ASCII和ISO8859-1等编码方式是十分相似的,只不过长度更长,编码空间(code space)更大。当时人们认为,2个字节形成的编码空间,也就是65535个code point (code point就是编码空间中的一个值,用以唯一标识一个字符)已经能包括世界上所有出现过和将要出现的字符了。于是乎,在Unicode的编码问题上,一个最直接的办法就是每个字符使用两个字节进行存储,这样code point跟编码间有着简单的一一对应关系(两者都是16位)。从这里开始,UTF-16登上了历史的舞台,但是现在的UTF-16是经过修正的,我们把最初未作修改的编码方式称作UCS2。但是,就象当年我们认为两个数字足够表示所在年份一样,Unicode的“65535问题”就像“千年虫问题”一样如期而至,我们慢慢发现,大千世界中出现的字符并不是简单的65535所能囊括的。历史再一次证明,我们应该用发展的眼光看待问题的产生和发展,任何静止的观点最终都会被历史所淘汰。(呵呵,政治没学好,凑合吧)
到了今天,Unicode已经是第五版了,它的编码空间是0x000000到0x10FFFF,共有1,114,112个。显然,此时直接使用最初的UTF-16编码方式是不合适的,因为code point 值大于0xFFFF的Unicode将无法编码。怎么办呢?说到这里,首先要明白一点,在Unicode中,并不是所有的code point都是分配给字符的,譬如在BMP(Base Multilingual Plane,也就是最初版本的2个字节组成的code space)中,就分为General Scripts Area,Symbols Area,CJK Miscellaneous Area,CJKV ideographs,Asian Scripts Area,Surrogates Area, Private use Area, Compatibility and Special Area等8个不同的区域,其中后面三个区域的code point都是没有分配给特定字符的(具体的介绍可以参看 Unicode Standard version 4.0 section 2.8)。好了,既然原来的字符空间装不下新增的字符,我们只好从其他区域借点地方过来了。于是,我们从原来的Private Use Area中把U+D800 - U+DFFF划分出来,专门用于UTF-16编码,这就是现在的Surrogates Area。有了Surrogates Area以后,UTF-16就有足够的空间来进行编码了:
1、对于code point 小于 0xFFFF的字符,我们可以直接一一对应进行编码,这跟原来是一致的。
2、对于code point 为 0x01000-10FFFF之间的字符使用两个字节表示,高字节的取值范围是U+D800-
U+DBFF,低字节的取值范围为U+DC00-U+DFFF。
那么,一个code point 是如何编码成两个符合规定的字节呢?具体算法如下:假定c的code point大于0xFFFF,则
high-byte = (0xD7C0 + (c >> 10));
low-byte = (0xDC00 | c & 0x3FF);
下面是一些编码结果:
\uD800\uDC00 = U-00010000
\uD800\uDC01 = U-00010001
\uD801\uDC01 = U-00010401
\uDBFF\uDFFF = U-0010FFFF
可以参看一下 Unicode Transformation Formats: UTF-8 & Co.