今天,用javascript脚本做了一个asp.net页面中的菜单工具,保存为menuscript.js. 在页面中用<script language="javascript" src="../js/menuscript.js"></script>调用, 结果在运行中奇怪的现象发生了:页面中的汉字正常显示,可菜单中的汉字却显示为乱码。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从unicode2.0开始,unicode项目采用了与iso 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。unicode协会现在的最新版本是2005年的unicode 4.1.0。iso的最新标准是10646-3:2003。
ucs规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由utf(ucs transformation format)规范规定的,常见的utf规范包括utf-8、utf-7、utf-16。
ietf的rfc2781和rfc3629以rfc的一贯风格,清晰、明快又不失严谨地描述了utf-16和utf-8的编码方法。我总是记不得ietf是internet engineering task force的缩写。但ietf负责维护的rfc是internet上一切规范的基础。
3、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个码位。
ucs-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作basic multilingual plane, 即bmp。或者说ucs-4中,高两个字节为0的码位被称作bmp。
将ucs-4的bmp去掉前面的两个零字节就得到了ucs-2。在ucs-2的两个字节前加上两个零字节,就得到了ucs-4的bmp。而目前的ucs-4规范中还没有任何字符被分配在bmp之外。
4、utf编码
utf-8就是以8位为单元对ucs进行编码。从ucs-2到utf-8的编码方式如下:
ucs-2编码(16进制) utf-8 字节流(二进制)
0000 - 007f 0xxxxxxx
0080 - 07ff 110xxxxx 10xxxxxx
0800 - ffff 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的unicode编码是6c49。6c49在0800-ffff之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6c49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即e6 b1 89。
读者可以用记事本测试一下我们的编码是否正确。
utf-16以16位为单元对ucs进行编码。对于小于0x10000的ucs码,utf-16编码就等于ucs码对应的16位无符号整数。对于不小于0x10000的ucs码,定义了一个算法。不过由于实际使用的ucs2,或者ucs4的bmp必然小于0x10000,所以就目前而言,可以认为utf-16和ucs-2基本相同。但ucs-2只是一个编码方案,utf-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、utf的字节序和bom
utf-8以字节为编码单元,没有字节序的问题。utf-16以两个字节为编码单元,在解释一个utf-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的unicode编码是594e,“乙”的unicode编码是4e59。如果我们收到utf-16字节流“594e”,那么这是“奎”还是“乙”?
unicode规范中推荐的标记字节顺序的方法是bom。bom不是“bill of material”的bom表,而是byte order mark。bom是一个有点小聪明的想法:
在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(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以ef bb bf开头的字节流,就知道这是utf-8编码了。
windows就是使用bom来标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是 "short overview of iso-iec 10646 and unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:
"understanding unicode a general introduction to the unicode standard" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=iws-chapter04a)
"character set encoding basics understanding character set encodings and legacy encodings" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=iws-chapter03)
我写过utf-8、ucs-2、gbk相互转换的软件包,包括使用windows api和不使用windows api的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。
我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。
新闻热点
疑难解答
图片精选