Unicode使全球化成为可能
Unicode支持世界上所有语言的编码和转换。
事实上,字母、数字和标点符号等所有字符在计算机中都是用数字来表示的。对字符进行编码有多种不同的方式。
随着计算机应用变得越来越全球化,推动了支持全世界各种语言字符的需求,很多人越来越清楚一个单一的统一编码方案是必要的。Unicode标准就是在这样的背景下诞生的。Unicode是XML的默认编码方案,是LDAP要求的编码方案,也是Java和Windows XP使用的基础字符集。多年前,你需要理解ASCII码才能成为一名成功的程序员。而今天,你只需要理解Unicode。
什么是Unicode?
Unicode是一个涵盖了目前全世界使用的所有已知字符的单一编码方案。每个字符都被分配了一个称为码点的不同数值。Unicode标准将字符组织成相关字符块。例如,Unicode使用从0x0400到0x04FF的码点块来表示俄国人和乌克兰人使用的西里尔文字及相关字母表。 (符号0x表示后面的字符是一个十六进制的数值。Unicode码点通常以十六进制表示。)文字是一个或多个相关书写系统或语言中使用的符号集合。因此,西里尔文字是不同西里尔字母表使用的所有字符的一个超集。Unicode还在不断发展,以增加它所支持的文字的数量,同时还包括新的字符,如欧元符号(?)。
Unicode定义了字符术语,表示能够传达意思的书面语言的最小组成部分。像A、B、C这样的字母是明显的字符示例,数字1、2、3也是。然而,"字符"这个术语可能有点模糊,而且你越多地使用Unicode,你就会发现自己越多地使用码点。考虑一下字符?,它由一个大写的字母E和一个分音符号组成。你可以把它看作一个字符,而实际上它对应于Unicode码点0x00CB。然而,Unicode还允许你分别处理?的两个组成部分:你可以将这个字符编码为大写字母E的码点0x0045,随后是组合分音符号(¨)的码点0x0308。这两个码点结合在一起形成了一个字符。Unicode定义了大量你可以用这种方式使用的组合变音符号和组合字符。
如何对字符进行编码
ASCII文本中,被分配给一个字符的数值还可用来表示内存中或磁盘上的该字符。字母A的ASCII码点是0x41。Unicode则会区别码点的值和该值在内存中的表示方式。例如,字母A的码点最终可能是0x41或0x0041,这只是一个简单的例子。码点在内存中如何表示是由Unicode的编码格式定义的。
Unicode定义了两种编码格式:UTF-8和UTF-16。UTF-8编码与ASCII向后兼容。任何合法的ASCII编码都会自动成为合法的UTF-8编码,使得将现有的ASCII数据库转换为Unicode数据库非常容易。UTF-8使用一个码点值来生成一个分布到一到四个字节上的位模式。
给定字符的UTF-8编码中的每个字节都被称为一个编码单元。标准ASCII字符集中字符的码点使用0x00到0x7F范围内的单一编码单元进行编码。大多数非亚洲文字用一个或两个编码单元(或字节)来表示每个字符。例如,乌克兰语的字母ghe的UTF-8表示为:
码点0x0490是:0xD2 0x90。亚洲文字一般要求每个字符包含三个编码单元。在Unicode 3.1中,定义了大量新的增补字符,每个字符需要包含四个UTF-8编码单元。
第二种重要的编码格式是UTF-16,它使用了一个双字节的编码单元。最重要的是,UTF-16和ASCII一样简单,因为编码单元的值与0x0000到0xFFFF范围内的任意码点的码点值相同。对于该范围内的字符,UTF-16是固定长度的双字节编码。再回过来看一下我们前面涉及ghe的例子,你可以使用直接与该字符的码点对应的编码单元0x0490在UTF-16中表示它。
在UTF-8和UTF-16之间进行选择
Oracle支持两种Unicode数据存储方法。第一种被称为Unicode数据库解决方案,它可以创建一个基于Unicode的数据库,使用UTF-8编码格式,不仅为CHAR和VARCHAR2字符数据类型进行编码,而且还对所有的SQL名和字面值进行编码。(这里CLOB是个特殊情况,因为Oracle 数据库10g在Unicode数据中总是使用UTF-16对CLOB进行编码。)为了实施Unicode数据库解决方案,将你的数据库字符集配置为AL32UTF8,这是UTF-8的Oracle名称。
另一种Unicode数据存储方法是Unicode数据类型解决方案,在该解决方案中UTF-16数据被保存在NCHAR、NVARCHAR和NCLOB等Unicode数据类型中。当你希望在非Unicode数据库的列级获得Unicode支持时,这是一个理想的解决方案。为了实施该解决方案,将你的国家字符集设置为AL16UTF16,这是UTF-16的Oracle名称。实际上,从Oracle9i数据库开始,将AL16UTF16作为国家字符集是默认的设置。
究竟使用UTF-8还是UTF-16需要考虑几个因素。Oracle甚至使你能够同时使用这两种解决方案。你的应用程序可能只处理CHAR和VARCHAR类型的数据,这可能会促使你选择Unicode数据库,因为如果需要改动的话,你在应用程序级别所做的改动将最少。在这种情况下,你的编码选择只能限于UTF-8,因为UTF-16只对Unicode数据类型有效。另一方面,在内部使用UTF-16的Windows或Java可能促使你选择UTF-16。
存储上的考虑可能使你倾向于其中的某个编码格式。一般来说,当大多数数据都是西欧字符时,UTF-8使用的空间比UTF-16少,因为在UTF-8中,这些数据的每个字符只需一个或最多两个字节。而当你的大多数数据是亚洲字符时,则与此正好相反,因为UTF-8要求每个亚洲字符至少三个字节,而UTF-16只使用两个字节对大多数亚洲字符进行编码。另外性能也是一个考虑因素,由于UTF-16的固定长度属性,使用UTF-16的字符串操作函数的性能往往优于UTF-8。
一般情况下,UTF-8得到了更广泛的使用,主要是因为互联网协议更适于在网络上传输面向字节的数据。UTF-8也是在整个数据库上提供Unicode支持的唯一解决方案,它使你能够在Unicode中定义元数据,如表名和用户名。
代理
Unicode标准的最初几个版本支持0x0000到0xFFFF范围内的65536个不同码点。这看起来似乎是很大数量的码点已经证实是不够的,而Unicode 3.0牺牲了其中的2048个码点来提供超过一百万(1024*1024)个新的增补码点,从旧范围结束的0x10000一直持续到0x10FFFF。 与这些新码点对应的字符被称为增补字符。
例如,0x1D11E表示音乐符号G (treble) clef:
UTF-8是长度可变的编码方式,它简单地用四个字节来对这些新码点进行编码,比以前最多三个字节还要多一个。然而,这样大的码点值无法用UTF-16所使用的双字节编码单元来表示。相反,UTF-16使用叫做代理对(surrogate pair)的两个编码单元组合来对增补码点进行编码。第一个这样的编码单元总是落在0xD800到0xDBFF范围内,被称为高代理码点。另一个编码单元则是低代理码点,落在0xDC00到0xDFFF范围内。这些定义明确的范围避免了判断给定编码单元是代理对的一部分还是独立单元时的不明确。
字符串处理
为了轻松地给Unicode值分配合适的存储空间,Oracle9i数据库引入了字符语义。你现在可以使用像VARCHAR2(3 CHAR)这样的声明,Oracle将留出适当的字节数来容纳基础字符集中的三个字符。至于AL32UTF8,Oracle将分配12个字节,因为UTF-8字符编码的最大长度是4个字节(3字符×4字节/字符=12字节)。另一方面,如果你使用AL16UTF16,你的声明将会是NVARCHAR2(3),Oracle只分配6个字节(3字符×2字节/字符=6字节)。一个值得注意的区别是,对于UTF-8,使用字符语义的声明可以为代理字符分配足够的空间,而对于UTF-16则不是这样。像NVARCHAR2(3)这样的声明为三个UTF-16编码单元提供空间,但一个单一的增补字符就可能消耗其中的两个编码单元。
为了帮助你创建和使用你无法从客户端软件输入或查看的码点组成的字符串,Oracle数据库提供了UNISTR和ASCIISTR函数。UNISTR使你能够在字符串中嵌入任意码点。例如,UNISTR('\0490\0435\043D\0438\043A')可以用原始的乌克兰语返回Jonathan的姓:
你可以通过调用ASCIISTR实现相反的操作,将字符串中的所有非ASCII字符转换为\xxxx格式的转义序列。
对于LENGTH、SUBSTR和INSTR等字符串函数,Oracle数据库为你提供了一些选择。当你使用UTF-8编码时,你可以基于码点对这些字符串函数进行操作,而这些码点是与字符或组合标记对应的。例如,LENGTH总是返回UTF-8编码的字符串中的码点数量。当你使用UTF-16时,规则稍有不同,你可以基于编码单元对LENGTH、SUBSTR和INSTR进行操作,从而可以将UTF-16作为固定宽度的字符集来对待。这是一个很小但却非常重要的区别,需要正确理解。应用于UTF-16编码字符串的LENGTH函数不会返回该字符串中码点的数量,而是编码单元的数量。这个区别可能会影响你对编码格式的选择。例如,假设在编写一个应用程序时希望字符串函数能够计算字符数,那么即使对于亚洲字符来说,采用UTF-8的成本也可能低于修改应用程序以处理编码单元的成本。
最后,你还可以使用COMPOSE和DECOMPOSE函数。这些函数的一个作用就是比较两个Unicode字符串。它们使你能够决定是否将预组合字符和其分解的等价字符看作是相同的还是不同的。例如,像DECOMPOSE (UNISTR('\00CB')) = DECOMPOSE(UNISTR ('E\0308'))这样的比较将被认为是正确的,因为DECOMPOSE将0x00CB转换为一个后面是编码单元0x0308的字母E。COMPOSE函数使你能够从反方向解决这一问题: COMPOSE(UNISTR('\00CB')) = COMPOSE(UNISTR ('E\0308'))也是正确的。
Unicode支持的一些文字是从右向左读的。阿拉伯语就是一个例子。Unicode标准规定字符永远按逻辑顺序存储,即你读取的顺序,而不管它的显示是从左到右还是从右到左。如果你按每次一个字符来构建Unicode字符串,就必须注意这一点。不要试图强行改变你字符串中字符的显示顺序。
多语言整理
每种语言都有自己的数据整理方法,Unicode使你能够在一个字符串中混合来自多种语言的字符。那么,你应怎样对这样的字符串进行整理?有关多语言整理的标准ISO-14651解决了应以什么样的方式对多语言数据进行整理的问题。Oracle通过使数据名以_M结束来对多语言数据进行整理的方式支持这个标准。这些整理方法中最常用的是GENERIC_M,它将按照ISO-14651描述的顺序对数据进行整理。在GENERIC_M中,一般将文字整理为由西方到东方的顺序,即以西欧语言使用的文字开始,以亚洲语言使用的文字结束。对增补字符的整理则在它们相关的文字中进行。
GENERIC_M对于大多数语言来说都适用,但有一些语言还是需要自己特殊的整理规则。为了满足这个需求,Oracle还提供了GENERIC_M的一些变体。例如,名为SCHINESE_STROKE_M的整理方法主要根据简体中文字的笔画数进行整理。当你使用这样一个针对特定语言的整理方式时,应该首先对该语言的组成部分--字符进行整理,其他字符则根据GENERIC_M规定的顺序整理。
字符集转换
阅读 关于Oracle字符语义的信息 |
很多情况下数据需要用不同的字符编码表示。从一种编码转换到另一种编码的过程就叫做字符集转换,在使用一种字符集的客户程序与使用另一种字符集存储数据的数据库通信时,最需要进行字符集转换。例如,你可能有一个客户程序正在使用Windows代码页1251,这是一种支持西里尔字母的编码格式。该客户程序可能访问了一个包含用UTF-8编码的西里尔数据的Oracle数据库。当客户程序查询该数据库时,任何返回到该客户程序的数据都自动从数据库的UTF-8编码转换为客户程序使用的Windows代码页1251编码。回忆一下我们前面提到的西里尔字母ghe (),它在UTF-8中用两个字节0xD2和0x90表示。这两个字节将被转换成0xA5,这是代码页1251中对ghe的表示。类似的,当数据从客户程序被发送到数据库时,就会进行相反的转换。Oracle数据库支持在其所有支持的字符集之间进行这样的字符集转换。当你选择的数据中包含一个在你的客户程序字符集中没有的Unicode字符时,这个字符将被修改为Oracle定义的替代字符,通常是一个问号(?)。
字符集转换还可以扩展到Unicode编码格式之间的转换。例如,如果你只使用UTF-8在CHAR列中存储Unicode数据,后来发现你需要存储很多亚洲字符,而使用UTF-16的话可以为你节约大量磁盘空间,上述特性就显得很有用了。Oracle提供了一个快速、轻松的移植解决方案,使你可以通过ALTER TABLE MODIFY语句将列的数据类型从CHAR转变为NCHAR,从而轻松地将编码从UTF-8改为UTF-16。你也可以进行相反的转换。从一种Unicode编码格式到另一种格式的转换是无缝进行的。
Oracle 数据库10g中的Unicode
Oracle 数据库10g完全支持最近发布的Unicode 3.2标准。除此以外,Unicode字符串的处理性能也比上一版本有所提高,Oracle还在字符集扫描程序中引入了自动语言检测特性,使你能够报告你的数据库内的语言使用情况。Oracle 数据库10g还添加了两个重要的特性来帮助Unicode数据库中的应用开发。第一个特性是支持Unicode的正则表达式模式匹配,该特性使你能够输入字符属性并执行复杂的语言搜索,包括对大小写/重音符号不敏感的搜索。
第二个特性是一种增强的整理算法,通过在NLS_SORT名后面添加'_CI'和'_AI',提供了对大小写和重音符号不敏感的搜索。字符串函数和正则表达式函数将得益于当前的NLS_SORT规则。在对大小写不敏感的排序中,正如Unicode标准中规定的那样,特殊字符的大小写变体都被认为是同等的。类似地,对重音符号不敏感的排序也将忽视基本字符上的重音符号。
回复Comments
作者:
{commentrecontent}