Unicode并不只是一个编程工具,它还是一个政治的、经济的工具。没有结合世界的语言支持的应用程序通常只能被那些能读写ASCII所支持语言的个人使用。这使得建立在ASCII基础之上的计算机技术脱离了世界上大部分人。Unicode允许程序使用世界上任何一种字符集,因此它支持所有语言。

Unicode让程序员为普通人提供用他们本国语言就能使用的软件。这样就不用再学一门外语了,而且更容易实现计算机技术社会和财政上的利益。很容易设想,如果用户必须为使用因特网浏览器而学习乌尔都语的话,您就难以看到计算机在美国的使用。Web就更不会出现了。

Linux承担了对Unicode很大程度上的支持。Unicode支持被嵌入到内核和代码开发库中。在很大程度上,使用程序中几句简单的命令就能将它们自动的结合到代码中。

所有现代字符集的基础都是在1968年以ANSIX3.4版本出版的美国信息交换标准码(AmericanStandardCodeforInformationInterchange,ASCII)。一个值得注意的例外是在ASCII之前定义的IBM的扩充的二进制编码的十进制交换码(ExtendedBinaryCodedDecimalInformationCode,EBCDIC)。ASCII是一个编码字符集(codedcharacterset,CCS),换句话说,它是整数到字符表示的映射。ASCII编码字符集允许用一个八位(基于二进制的,用值0或1表示的)字段或字节(2^8=256)表示256个字符。这是一个高度受限的编码字符集,它不能表示许多不同语言的所有字符(如中文和日文),不能表示科学符号,更不能表示古代文字(神秘符号和象形文字)和音乐符号。通过更改一个字节的长度而使更大的字符集得以被编码,这似乎有效但完全不切实际。所有的计算机都基于八位字节。解决方法是一种字符编码方案(Characterencodingscheme,CES)―用定长或变长的多字节序列能够表示比256大的数.这些数值接着通过编码字符集被映射到它们表示的字符。

Unicode的定义

Unicode通常用作涉及双字节字符编码方案的通用术语。UnicodeCCS3.1的官方称谓是ISO10646-1通用多八字节编码字符集(UniversalMultipleOctetCodedCharacterSet,UCS)。Unicode3.1版本添加了44,946个新的编码字符。算上Unicode3.0版本已经存在的49,194个字符,共计94,140个。

Unicode编码字符集利用了一个由128个三维的组构成的四维编码空间。其中每个组包含256个二维平面。每个平面由256个一维的行组成,并且每个行有256个单元。每个单元在这个编码空间内对一个字符编码,或者被声明为未经使用。这种编码概念被称为UCS-4;四个八位元用来表示指定组、平面、行和单元的每个字符。

第一个平面(第00组的第00平面)是基本多语言平面(BasicMultilingualPlane,BMP)。BMP按字母、音节、表意符号和各种符号及数字定义了常规使用的字符。后续的平面用于附加字符或其它还没有发明的编码实体。我们需要这完整的范围去处理世界上的所有语言;特别是拥有将近64,000个字符的一些东亚语言。

BMP被用作双字节的编码字符集,这种编码字符集确定为ISO10646UCS-2格式。ISO10646UCS-2就是指Unicode(并且两者相同)。BMP,像所有UCS平面那样,包含了256行,其中每行包含256个单元,字符仅仅按照BMP中的行和单元的八位元在单元中被编码。这就允许16位编码字符能够被用来书写大多数商业上最重要的语言。UCS-2不需要代码页切换、代码扩展或代码状态。UCS-2是一种将Unicode结合到软件中的简单方法,但它只限于支持UnicodeBMP。

若要用8位字节表示一个多于2^8=256个字符的字符编码系统(charactercodingsystem,CCS),就需要一种字符编码方案(character-encodingscheme,CES)。


Unicode转换

在UNIX中,使用得最多的字符编码方案是UTF-8。它考虑到了对整个Unicode全部页和平面的全面支持,而且它仍能正确的识别ASCII。除了UTF-8的其他选择还有:UCS-4、UTF-16、UTF-7.5、UTF-7、SCSU、HTML和JAVA。

Unicode转换格式(UnicodeTransformationFormats,UTFs)是一种通过映射多字节编码中的值来支持Unicode的字符编码方案。本文将分析最流行的格式―UTF-8字符编码系统。

UTF-8

UTF-8转换格式正逐步成为一种占主导地位的交换国际文本信息的方法,因为它可以支持世界上所有的语言,而且它还与ASCII兼容。UTF-8使用变长编码。从0到0x7f(127)的字符把自身编码成单字节,而将值更大的字符编码成2到6个字节。

表1.UTF-8编码

0x00000000-0x0000007F:0xxxxxxx
0x00000080-0x000007FF:110xxxxx10xxxxxx
0x00000800-0x0000FFFF:1110xxxx10xxxxxx10xxxxxx
0x00010000-0x001FFFFF:11110xxx10xxxxxx10xxxxxx10xxxxxx
0x00200000-0x03FFFFFF:111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
0x04000000-0x7FFFFFFF:1111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

字节10xxxxxx是一个扩展字节,它的xxxxxx位位置被以二进制表示的字符代码号的位所填充。这是能够代表被使用代码的最短的可能的多字节序列。

UTF-8编码示例

Unicode字符版权标记字符0xA9=10101001用UTF-8编码如下所示:

1100001010101001=0xC20xA9

“不等于”符号字符0x2260=0010001001100000编码如下所示:

111000101000100110100000=0xE20x890xA0

通过获取continuationbyte的值可以看到原始数据:

[1110]0010[10]001001[10]100000
0010001001100000
0010001001100000=0x2260

第一个字节定义后面紧跟的八位元数,如果是7F或更小,这就是等价的ASCII值。每个八位字节以10xxxxxx开头,确保字节不与ASCII的值混淆。


UTF支持

在Linux平台上使用UTF-8之前,请确信分发包里有glibc2.2和XFree864.0或更新的版本。早先的版本缺少UTF-8语言环境支持和ISO10646-1X11字体。

在UTF-8发布之前,Linux用户使用各种不同特定语言的扩展ASCII,像欧洲用户用ISO8859-1或ISO8859-2,希腊用户使用ISO8859-7,俄罗斯用户使用KOI-8/ISO8859-5/CP1251(西里尔字母)。这使得数据交换出现了很多问题,并且需要为这些编码之间的差异编写应用软件。这种语言支持是不完善的,而且数据交换没有经过测试。Linux主要的发行商和应用程序开发者正致力于让主要以UTF-8格式表示的Unicode成为Linux中的标准。

为了识别Unicode文件,Microsoft建议所有的Unicode文件应该以ZEROWIDTHNOBREAKSPACE(U+FEFF)字符开头。这作为一个“特征符”或“字节顺序标记(byte-ordermark,BOM)”来识别文件中使用的编码和字节顺序。但是,Linux/UNIX并没有使用BOM,因为它会破坏现有的ASCII文件的语法约定。在POSIX系统中,选中的语言环境识别了在一个过程中的所有输入输出文件期望的编码形式。

有两种方法可以将UTF-8支持添加到Linux应用程序中。第一种方法,数据都以UTF-8形式存放在各处,这样软件改动很少(被动的)。另一种方法,被读取的UTF-8数据用标准的C语言库函数转变成为宽字符数组(转换的)。在输出时,用函数wcsrtombs()使字符串被转变回UTF-8:


清单1.wcsrtombs()

#include <wchar.h> 
size_t wcsrtombs (char *dest, const wchar_t **src, size_t len, mbstate_t *ps);

方法的选择取决于应用程序的性质。大多数应用程序可以使用被动的方法操作。这就是在UNIX平台上使用UTF-8会如此流行的原因。像catecho那样的程序就不需要修改。字节流仍只是字节流,并没有对它进行任何处理。ASCII字符和控制代码在UTF-8语言环境中不改变。

通过字节计数对字符进行计数的程序需要一些小小的改动。在UTF-8中应用程序不对任何扩展的字节进行计数。如果选择了UTF-8语言环境,C语言库的strlen(s)函数需要用mbstowcs()函数来代替:


清单2.mbstowcs()函数

#include <stdlib.h>
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n);

strlen的一种常见用法是估算显示宽度。中文和其它表意符号将占用两列位置。wcwidth()函数用来测试每个字符的显示宽度:


清单3.wcwidth()函数

#include <
        wchar.h> 
int wcwidth(wchar_t wc);
      


Unicode的C语言支持

在正式情况下,从GNUglibc2.2开始,wchar_t类型只为32位的ISO10646格式数值所特定使用,与当前使用的语言环境无关。通过ISOC99所要求的__STDC_ISO_10646__宏的定义作为信号通知应用程序。__STDC_ISO_10646__的定义用来指出wchar_t是Unicode。精确的值是一个十进制的yyyymmL格式的常数。例如,使用:


清单4.指出wchar_t是Unicode

#define __STDC_ISO_10646__ 200104L

是为指出wchar_t类型的值是由ISO/IEC10646和到指定的年月为止的所有修正与技术勘误定义的字符编码表示。

对wchar_t的利用如这个示例所示,使用宏确定在ISOC99可移植代码中写双引号的方法。


清单5.确定写双引号的方法

#if __STDC_ISO_10646__  
   printf("%lc", 0x201c);  
#else  
   putchar('"');  
#fi

语言环境

激活UTF-8的恰当的办法是POSIX语言环境机制。语言环境是一种包含有关软件行为特定文化约定的配置设定。它包含了字符编码、日期/时间符号、分类规则以及度量系统。语言环境的名称通常由ISO639-1语言、ISO3166-1国家或地区代码以及可选的编码名称和其它限定符组成。您可以用命令locale-a获取所有安装在系统上的语言环境列表(通常在/usr/lib/locale/)。

如果没有预安装UTF-8语言环境,你可以用localedef命令生成它。若要为某个特定用户生成并激活一个德语的UTF-8语言环境,请使用如下语句:


清单6.为特定用户生成语言环境

localedef -v -c -i de_DE -f UTF-8 $HOME/local/locale/de_DE.UTF-8
export LOCPATH=$HOME/local/locale
export LANG=de_DE.UTF-8

有时候为所有用户添加UTF-8语言环境会很有用。root用户使用如下指令就可以完成:


清单7.为每个用户生成语言环境

localedef -v -c -i de_DE -f UTF-8 /usr/share/locale/de_DE.UTF-8

若要为每个用户将这个语言环境设为缺省值,可以将以下行添加到/etc/profile文件中:


清单8.为所有用户设置缺省的语言环境

export LANG=de_DE.UTF-8

处理多字节字符代码序列的函数行为依赖于当前语言环境的LC_CTYPE类别;它确定了依赖语言环境的多字节编码。值LANG=de_DE(德语)会导致输出按ISO8859-1被格式化。值LANG=de_DE.UTF-8会把输出格式化成UTF-8。语言环境设置会导致printf中的%ls格式说明符调用wcsrtombs()函数以便于将宽字符的参数字符串转换成依赖语言环境的多字节编码。语言环境中的国家或地区标识符如:LC_CTYPE=en_GB(英国英语)和LC_CTYPE=en_AU(澳大利亚英语),它们之间的差异只在LC_MONETARY类别中,原因在于货币的名称和打印货币数量的规则不同。

请给您首选的语言环境设置环境变量LANG。当一个C程序执行setlocale()函数时:


清单9.setlocale()函数

#include <stdio.h>
#include <locale.h>
//char *setlocale(int category, const char *locale);
int main()
{
  if (!setlocale(LC_CTYPE, "")) 
  {
    fprintf(stderr, "Locale not specified. Check LANG, LC_CTYPE, LC_ALL.
");
    return 1;
  }

C语言库将会依次测试环境变量LC_ALL、LC_CTYPE和LANG。其中第一个含值的环境变量将决定为LC_CTYPE类别装入哪种语言环境数据。语言环境数据分裂成独立的类别。值LC_CTYPE定义了字符编码,而LC_COLLATE定义了排序顺序。我们用LANG环境变量为所有类别设置缺省语言环境,但LC_*变量可以用来覆盖单个类别。

您可以用命令localecharmap查询当前语言环境中字符编码的名称。如果您从LC_CTYPE类别中成功选取了UTF-8语言环境,会输出UTF-8。命令locale-m提供一张已安装的所有字符编码名称的列表。

如果您使用专门的C语言库的多字节函数来完成所有外部字符编码和内部使用的wchar_t编码之间的转换,那么C语言库将承担责任,根据LC_CTYPE使用正确的编码方式。这甚至不需要程序被明确的编码成当前的多字节编码。

如果需要一个应用程序能明确的支持UTF-8(或其它编码)转换方法而不用libc多字节函数,则应用程序必须确定是否需要激活UTF-8模式。带有<langinfo.h>库头文件的与X/Open兼容系统可以用如下代码:


清单10.检测当前的语言环境是否使用了UTF-8编码

BOOL utf8_mode = FALSE;
if( !  strcmp(nl_langinfo(CODESET), "UTF-8")
   utf8_mode = TRUE;

为检测当前语言环境是否使用了UTF-8编码。首先必须调用setlocale(LC_CTYPE,"")函数,依据环境变量设置语言环境。nl_langinfo(CODESET)函数也是由localecharmap命令调用,从而查找当前语言环境指定的编码名称。

另一种可以使用的方法是查询语言环境变量:


清单11.查询语言环境变量

char *s;
BOOL utf8_mode = FALSE;
if ((s = getenv("LC_ALL")) || (s = getenv("LC_CTYPE")) || (s = getenv ("LANG"))) 
{
   if (strstr(s, "UTF-8"))
      utf8_mode = TRUE;
}

这项测试假设UTF-8语言环境名称中有值“UTF-8”,但实际情况并不总是如此,所以应该使用nl_langinfo()方法。


总结

为支持世界上的所有语言,需要一种具有八位字节字符编码策略的字符编码系统,它的字符应多于ASCII(一种使用无符号字节的扩展版本)的2^8=256个字符。Unicode就是这样一种字符编码系统,它具有由128个三维组(带有由大量字符编码方案的方法支持的94,140个定义好的字符值)组成的四维编码空间,在Linux中更流行的字符编码方案是Unicode转换格式UTF-8。

参考资料

FROM:http://www.ibm.com/developerworks/cn/linux/i18n/unicode/linuni/