Unicode编码
Unicode Encodings
有许多编码定义了Unicode数据的存储方式,包括定长和变长两种。一个定长编码,是指每个码点包含固定的字节数;而一个可变长度编码,是指不同的字符可以用不同数量的字节来表示。UTF-32和UCS2是定长的,UTF-7和UTF-8是可变长度的, 而UTF-16是一种可变长度编码,但通常看起来像一个定长编码。
UTF-32(以及UCS4,两者几乎相同)编码每个码点用4个字节,因此可以在U+0000和U+FFFFFFFF之间对任何码点编码。这通常没有必要,因为根本没有定义那么多的码点。UCS2用2个字节编码每个码点,因此可以使用介于U+0000和U+FFFF之间的内容编码任何码点。UTF-16对于大多数字符也使用2个字节,但在U+D800和U+DFFF之间
被当作所谓的代理对来使用,它使得UTF-16能使用U+0000和U+10FFFF之间的值来对码点进行编码。
UTF-8使用1到4个(ISO 10646版本是1到7,接下来会讨论这个版本)字节来为码点编码,而且能够编码介于U+0000和U+0FFFF之间的码点(ISO 10646版本是U+0000到U+3FFFFFFFFFF)。待会儿我们将会更详尽地讨论UTF-8。UTF-7是7位安全编码,它能够直接用于电子邮件编码,而不需要通过base64或quoted-printable编码进行转换。UTF-7从未真正流行和被广泛使用,因为它缺乏UTF-8的ASCII码透明性,而且quoted-printable已经足以应付电子邮件传输UTF-8编码的需要了。
那么,我们一直提及的ISO 10646是什么? Unicode显然是一个好主意,所以有两个组织同时开展这方面的工作,分别是Unicode联盟和国际标准化组织(ISO)。在发布前,标准进行了合并,但仍然保留了单独的名字。随着时间的推移,它们之间大多保持同步,但在编码问题里有不同的文档和一些分歧。为保持清晰,我们将它们视为同一个标准。
最重要的一点是,我们虽然有多种编码方式(将码点映射到字节),但我们只有一个单一的字符集(将字符映射到码点)。这是Unicode核心的概念。有一组单一的码点可以供所有应用程序使用,提供多重编码方式,使应用程序按照合适的方式存储数据。所有的Unicode编码都是完整的,我们可以随时从一个转换到另一个,而不丢失任何信息(这里不考虑这个事实:许多私用的码点不能用UTF-16表示,而UTF-32可以)。在Unicode中,无论具体采用什么编码来存放它,码点U+09E0总是代表孟加拉语的元音字符RR。
码点和字符, 字形和字素
Code Points and Characters, Glyphs and Graphemes
到目前为止,我们已经描绘出一幅相当复杂的画面——字母是有公认含义的标识符,并且通过码点来表示。一个码点可以用一个或者多个字节通过某种编码来表示。但实际情况要更为复杂。一个字符并不一定反映人们日常所说的字符。例如,带有发音符号的拉丁字母“a”可以用码点U+00E3(带有发音符号的拉丁小写字母“a”)来表示,也可以用两个码点来组合表示,U+0061(拉丁小写字母“a”)和U+0303(加上发音符号)。这种组合形式被称为字素。一个字素由一个或者多个字符组合而成——基本字符加零个或多个附加字符。再加上连字,情况就更复杂了,这时一个单独的字形可能由两个或者三个字符形成。这些字符接着由一个单独的码点,或者两个正常的码点来表示。比如,连字fi(“f”后面跟“i”)可以用U+0066(小写拉丁字母“f”)和U+0131(不带点的小写拉丁字母“i”)
来表示,或者由U+FB01(小写的拉丁连字“fi”)来表示。
这些在实际应用上意味着什么? 这意味着给定一串码点流,不能随意对它们分段(就像substring函数那样)以得到预期的音素序列。这也意味着有超过一种的方法来表示单个字母,可用不同序列的连字符和字符相结合,来形成相同的音素(虽然Unicode标准化规则允许性能良好的分解状态的音素比较)。想要知道使用UTF-8编码的字符串中的字符数(长度)时,我们不能依赖于字节数。我们甚至不能依赖于码点,因为有一些码点可组合成不增加额外字形的字符。你需要了解这两方面的内容,一是码点位于字节流何处,二是码点属于什么字符类别。将Unicode中定义的字符类别列于表4-1。
表4-1:Unicode基本分类
|
Codes |
Descriptions |
|
Lu |
Letter, uppercase |
|
Ll |
Letter, lowercase |
|
Lt |
Letter, titlecase |
|
Lm |
Letter, modifier |
|
Lo |
Letter, other |
|
Mn |
Mark, nonspacing |
|
Mc |
Mark, spacing combining |
|
Me |
Mark, enclosing |
|
Nd |
Number, decimal digit |
|
Nl |
Number, letter |
|
No |
Number, other |
|
Zs |
Separator, space |
|
Zl |
Separator, line |
|
Zp |
Separator, paragraph |
|
Cc |
Other, control |
|
Cf |
Other, format |
|
Cs |
Other, surrogate |
|
Co |
Other, private use |
|
Cn |
Other, not assigned(including noncharacters) |
|
Pc |
Punctuation, connector |
|
Pd |
Punctuation, dash |
|
Ps |
Punctuation, open |
|
Pe |
Punctuation, close |
续表
|
Codes |
Descriptions |
|
Pi |
Punctuation, initial quote(may behave like Ps or Pe depending on usage) |
|
Pf |
Punctuation, final quote(may behave like Ps or Pe depending on usage) |
|
Po |
Punctuation, other |
|
Sm |
Symbol, math |
|
Sc |
Symbol, currency |
|
Sk |
Symbol, modifier |
|
So |
Symbol, other |
事实上,Unicode不仅仅确定了每个字符的大类。它还规定了姓名、一般特性(英文字母和方块等)、成形信息(双向、镜似等)、字体(大写、小写等)、数值、标准化属性、边界和其他全部有用的资料。这些多半不是我们关注的内容,而使用这些信息时,我们也意识不到正在用它们。因为这一切都在幕后神奇地进行,但值得注意的是,除了码点本身之外,Unicode标准的核心部分就是这些属性。
这些属性和特点,连同标准化规则,都可以从Unicode协会网站(http://www.unicode.org/)获得,它同时提供了人和计算机可读的格式。
字节顺序标记
Byte Order Mark
一个字节顺序标记(BOM)是一组字节序列,它们出现在Unicode流开端,说明编码类型。因为系统可能是大尾字节序(big endian),也可能是小尾字节序(little endian),或者是多字节的Unicode编码,例如UTF-16可采用任何一种方式来储存组成码点的多个字节(最高或最低字节优先)。BOM把码点U+FEFF(为此目的预留)置于文件开端,利用它来判断字节序。字节的实际输出取决于使用的编码,在读取Unicdoe流的头4个字节后,就可以确认所用的编码(见表4-2)。
表4-2:常用Unicode编码的BOM
|
Encoding |
Byte order mark |
|
UTF-16 big endian |
FE FF |
|
UTF-16 little endian |
FF FE |
|
UTF-32 big endian |
00 00 FE FF |
|
UTF-32 little endian |
FF FE 00 00 |
|
UTF-8 little endian |
EF BB BF |
大多数其他的Unicode编码有自己的BOM(包括SCSU、UTF-7和UTF-EBCDIC),也都表示码点U+FEFF。BOM应避免用在HTML和XMLdocuments的开端,这会让一些浏览器无法正常解析。因为PHP不会接受,所以您也应该避免在PHP模板或源代码文件的开端放置BOM,即使它们可能采用了UTF-8编码。
想要更多关于Unicode标准的资料,您可以访问Unicode协会的Unicode网站,网址是http://www.unicode.org/,或者再买本书——《Unicode标准4.0》(Addison-Wesley)(这本书很有趣,它包含了当前所有的98 000个Unicode码点),你可以从http://www.unicode. org/book/bookform.html订购这本书。
UTF-8编码
The UTF-8 Encoding
UTF-8是大多数Web应用开发人员喜爱的编码方式,其全名是Unicode 8-bit转换格式. UTF-8是一个可变长度的编码,适合用于紧凑地存储拉丁字母。对那些字母,它比大固定宽度编码(如UTF-16码)节省空间,而且也能支持大范围的码点。UTF-8和ASCII (又称ISO 646标准)完全兼容。因为ASCII编码定义的代码只有0到127 (使用字节的头7位),UTF-8原样保留所有这些编码,而将高位比特则用于更高的码点。
UTF-8将码点的相应表示的长度(按字节计)编码到首字节中,然后使用后续的字节添加相应长度的表示码点的比特。UTF-8字节编码序列中,每个字节都贡献0至7比特给最终的码点,总体上形成从左到右的一个长二进制数字。这些字节基于表 4-3组合成每个码点的二进制表示。
图4-3:UTF-8 字节布局
|
字节数 |
位数 |
表示 |
|
1 |
7 |
0bbbbbbb |
|
2 |
11 |
110bbbbb 10bbbbbb |
|
3 |
16 |
1110bbbb 10bbbbbb 10bbbbbb |
|
4 |
21 |
11110bbb 10bbbbbb 10bbbbbb 10bbbbbb |
|
5 |
26 |
111110bb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb |
|
6 |
31 |
1111110b 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb |
|
7 |
36 |
11111110 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb |
|
8 |
42 |
11111111 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb 10bbbbbb |
这意味着对于码点U+09E0(我最喜欢的孟加拉元音字幕RR),我们要用三个字节,因为它需要表示12个比特的数据(十六进制的09E0在二进制中是100111100000)。我们将这些比特的数据和比特掩码组合起来就得到11100000 10100111 10100000 或者说0xE0 0xA7 0xA0 (也许你已经可以从前例中认识到这点)。
UTF-8设计之中的一个优点是它将一组码点编码成字节流,而不是WORDS或者DWORDS,这样可以忽略底层机器的字节序(endian)问题。这意味着你可以在两台小尾字节序和大尾字节序的机器之间交换UTF-8流而不需要任何字节重组或添加BOM。也就是说,你可以完全无视底层的体系架构。
UTF-8编码的另一个优点源于它从左到右存储实际码点的比特,做一个二进制形式的原始字节排序,就可以将字符串按码点排序。这虽然不如按locale排序规则进行排序那么好,但对于无需理解UFF-8的底层系统来说,它终究提供了很简易的一种排序方式,底层系统只需要知道如何排序原始字节就可以了。






