浅谈文字编码和Unicode(上)

  • Post author:
  • Post category:其他



浅谈文字编码和Unicode(上)


我曾经写过一篇



《谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词》



(以下简称《谈谈Unicode编码》),在网上流传较广,我也收到不少朋友的反馈。本文探讨《谈谈Unicode编码》中未介绍或介绍较少的代码页、Surrogates等问题,补充一些Unicode资料,顺带介绍一下我最近编写的一个Unicode工具:UniToy。本文虽然是前文的补充,但在写作上尽量做到独立成篇。


标题中的“浅谈”是对自己的要求,我希望文字能尽量浅显易懂。但本文还是假设读者知道字节、16进制,了解《谈谈Unicode编码》中介绍过的字节序和Unicode的基本概念。


0 UniToy


UniToy是我编写的一个小工具。通过UniToy,我们可以全方位、多角度地查看Unicode,了解Unicode和语言、代码页的关系,完成一些文字编码的相关工作。本文的一些内容是通过UniToy演示的。大家可以从我的网站(



www.fmddlmyy.cn



)下载UniToy的



演示版本





1 文字的显示


1.1 发生了什么?


我们首先以Windows为例来看看文字显示过程中发生了什么。用记事本打开一个文本文件,可以看到文件包含的文字:


字符和编码


如果我们用UltraEdit或Hex Workshop查看这个文件的16进制数据,可以看到:


D7D6 B7FB BACD B1E0 C2EB


我们看到:文件“例子GBK.txt”有10个字节,依次是“D7 D6 B7 FB BA CD B1 E0 C2 EB”,这就是记事本从文件中读到的内容。记事本是用来打开文本文件的,所以它会调用Windows的文本显示函数将读到的数据作为文本显示。Windows首先将文本数据转换到它内部使用的编码格式:Unicode,然后按照文本的Unicode去字体文件中查找字体图像,最后将图像显示到窗口上。总结一下前面的分析,文字的显示应该是这样的:


  • 步骤1:文字首先以某种编码保存在文件中。

  • 步骤2:Windows将文件中的文字编码映射到Unicode。

  • 步骤3:Windows按照Unicode在字体文件中查找字体图像,画到窗口上。


所谓编码就是用数字表示字符,例如用D7D6表示“字”。当然,

编码

还意味着

约定

,即大家都认可。从《谈谈Unicode编码》中,我们知道Unicode也是一种文字编码,它的特殊性在于它是由国际组织设计,可以容纳全世界所有语言文字。而我们平常使用的文字编码通常是针对一个区域的语言、文字设计,只支持特定的语言文字。例如:在上面的例子中,文件“例子GBK.txt”采用的就是GBK编码。


如果上述3个步骤中任何一步发生了错误,文字就不能被正确显示,例如:


  • 错误1:如果弄错了编码,例如将Big5编码的文字当作GBK编码,就会出现乱码。




  • 错误2:如果从特定编码到Unicode的映射发生错误,例如文本数据中出现该编码方案未定义的字符,Windows就会使用缺省字符,通常是?。




  • 如果当前字体不支持要显示的字符,Windows就会显示字体文件中的缺省图像:空白或方格。


在Unicode被广泛使用前,有多少种语言、文字,就可能有多少种文字编码方案。一种文字也可能有多种编码方案。那么我们怎么确定文本数据采用了什么编码?


1.2 采用了哪种编码?


按照惯例,文本文件中的数据都是文本编码,那么它怎么表明自己的编码格式?在记事本的“打开”对话框上:




我们可以看到记事本支持4种编码格式:ANSI、Unicode、Unicode big endian、UTF-8。如果读者看过《谈谈Unicode编码》,对Unicode、Unicode big endian、UTF-8应该不会陌生,其实它们更准确的名称应该是UTF-16LE(Little Endian)、UTF-16BE(Big Endian)和UTF-8,它们是基于Unicode的不同编码方案。


在《谈谈Unicode编码》中介绍过,Windows通过在文本文件开头增加一些特殊字节(BOM)来区分上述3种编码,并将没有BOM的文本数据按照ANSI代码页处理。那么什么是代码页,什么是ANSI代码页?


2 代码页和字符集


2.1 Windows的代码页

2.1.1 代码页


代码页(Code Page)是个古老的专业术语,据说是IBM公司首先使用的。代码页和字符集的含义基本相同,代码页规定了适用于特定地区的字符集合,和这些字符的编码。可以将代码页理解为字符和字节数据的映射表。


Windows为自己支持的代码页都编了一个号码。例如代码页936就是简体中文 GBK,代码页950就是繁体中文 Big5。代码页的概念比较简单,就是一个字符编码方案。但要说清楚Windows的ANSI代码页,就要从Windows的区域(Locale)说起了。

2.1.2 区域和ANSI代码页


微软为了适应世界上不同地区用户的文化背景和生活习惯,在Windows中设计了区域(Locale)设置的功能。Local是指特定于某个国家或地区的一组设定,包括代码页,数字、货币、时间和日期的格式等。在Windows内部,其实有两个Locale设置:系统Locale和用户Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。我们可以在控制面板的“区域和语言选项”中设置系统Locale和用户Locale:




每个Locale都有一个对应的代码页。Locale和代码页的对应关系,大家可以参阅我的另一篇文章



《谈谈Windows程序中的字符编码》



的附录1。系统Locale对应的代码页被作为Windows的默认代码页。在没有文本编码信息时,Windows按照默认代码页的编码方案解释文本数据。这个默认代码页通常被称作ANSI代码页(ACP)。


ANSI代码页还有一层意思,就是微软自己定义的代码页。在历史上,IBM的个人计算机和微软公司的操作系统曾经是PC的标准配置。微软公司将IBM公司定义的代码页称作OEM代码页,在IBM公司的代码页基础上作了些增补后,作为自己的代码页,并冠以ANSI的字样。我们在“区域和语言选项”高级页面的代码页转换表中看到的包含ANSI字样的代码页都是微软自己定义的代码页。例如:


  • 874 (ANSI/OEM – 泰文)

  • 932 (ANSI/OEM – 日文 Shift-JIS)

  • 936 (ANSI/OEM – 简体中文 GBK)

  • 949 (ANSI/OEM – 韩文)

  • 950 (ANSI/OEM – 繁体中文 Big5)

  • 1250 (ANSI – 中欧)

  • 1251 (ANSI – 西里尔文)

  • 1252 (ANSI – 拉丁文 I)

  • 1253 (ANSI – 希腊文)

  • 1254 (ANSI – 土耳其文)

  • 1255 (ANSI – 希伯来文)

  • 1256 (ANSI – 阿拉伯文)

  • 1257 (ANSI – 波罗的海文)

  • 1258 (ANSI/OEM – 越南)


在UniToy中,我们可以按照代码页编码顺序查看这些代码页的字符和编码:




我们不能直接设置ANSI代码页,只能通过选择系统Locale,间接改变当前的ANSI代码页。微软定义的Locale只使用自己定义的代码页。所以,我们虽然可以通过“区域和语言选项”中的代码页转换表安装很多代码页,但只能将微软的代码页作为系统默认代码页。

2.1.3 代码页转换表


在Windows 2000以后,Windows统一采用UTF-16作为内部字符编码。现在,安装一个代码页就是安装一张代码页转换表。通过代码页转换表,Windows既可以将代码页的编码转换到UTF-16,也可以将UTF-16转换到代码页的编码。代码页转换表的具体实现可以是一个以nls为后缀的数据文件,也可以是一个提供转换函数的动态链接库。有的代码页是不需要安装的。例如:Windows将UTF-7和UTF-8分别作为代码页65000和代码页65001。UTF-7、UTF-8和UTF-16都是基于Unicode的编码方案。它们之间可以通过简单的算法直接转换,不需要安装代码页转换表。


在安装过一个代码页后,Windows就知道怎样将该代码页的文本转换到Unicode文本,也知道怎样将Unicode文本转换成该代码页的文本。例如:UniToy有导入和导出功能。所谓导入功能就是将任一代码页的文本文件转换到Unicode文本;导出功能就是将Unicode文本转换到任一指定的代码页。这里所说的代码页就是指系统已安装的代码页:




其实,如果全世界人民在计算机刚发明时就统一采用Unicode作为字符编码,那么代码页就没有存在的必要了。可惜在Unicode被发明前,世界各国人民都发明并使用了各种字符编码方案。所以,Windows必须通过代码页支持已经被广泛使用的字符编码。从这种意义看,代码页主要是为了兼容现有的数据、程序和习惯而存在的。

2.1.4 SBCS、DBCS和MBCS


SBCS、DBCS和MBCS分别是单字节字符集、双字节字符集和多字节字符集的缩写。SBCS、DBCS和MBCS的最大编码长度分别是1字节、两字节和大于两字节(例如4或5字节)。例如:代码页1252 (ANSI-拉丁文 I)是单字节字符集;代码页936 (ANSI/OEM-简体中文 GBK)是双字节字符集;代码页54936 (GB18030 简体中文)是多字节字符集。


单字节字符集中的字符都用一个字节表示。显然,SBCS最多只能容纳256个字符。


双字节字符集的字符用一个或两个字节表示。那么我们从文本数据中读到一个字节时,怎么判断它是单字节字符,还是双字节字符的首字符?答案是通过字节所处范围来判断。例如:在GBK编码中,单字节字符的范围是0x00-0x80,双字节字符首字节的范围是0x81到0xFE。我们顺序读取字节数据,如果读到的字节在0x81到0xFE内,那么这个字节就是双字节字符的首字节。GBK定义双字节字符的尾字节范围是0x40到0x7E和0x80到0xFE。


GB18030是多字节字符集,它的字符可以用一个、两个或四个字节表示。这时我们又如何判断一个字节是属于单字节字符,双字节字符,还是四字节字符?GB18030与GBK是兼容的,它利用了GBK双字节字符尾字节的未使用码位。GB18030的四字节字符的第一字节的范围也是0x81到0xFE,第二字节的范围是0x30-0x39。通过第二字节所处范围就可以区分双字节字符和四字节字符。GB18030定义四字节字符的第三字节范围是0x81到0xFE,第四字节范围是0x30-0x39。


2.2 代码页实例

2.2.1 实例一:GB18030代码页


1.1节的“错误2”中演示了一个全被显示成’?’的文件。这个文件的数据是:




其实,这是一个包含了6个四字节字符的GB18030编码的文件。记事本按照GBK显示这些数据,而GB18030的四字节字符编码在GBK中是未定义的。Windows根据首字节范围判断出12个双字节字符,然后因为找不到匹配的转换而将其映射到默认字符’?’。使用UniToy按照GB18030代码页导入这个文件,就可以看到:




这个GB18030编码的文件是用UniToy创建的,编辑Unicode文本,然后导出到GB18030编码格式。

2.2.2 实例二:GBK和Big5的转换


综合使用UniToy的导入、导出功能就可以在任意两个代码页之间转换文本。其实,由于各代码页支持的字符范围不同,我们一般不会直接在代码页间转换文本。例如将以下GBK编码的文本:




直接转换到Big5编码,就会看到:




变成’?’的字符都是Big5编码不支持的简化字。在从Unicode转换到Big5编码时,由于Big5编码不支持这些字符,Windows就用默认字符’?’代替。在UniToy中,我们可以先将简体字转换到繁体字,然后再导出到Big5编码,就可以正常显示:




同理,将Big5编码的文本转换到GBK编码的步骤应该是:


  • 将Big5编码的文本导入到Unicode文本;

  • 将繁体的Unicode文本转换简体的Unicode文本;

  • 将简体的Unicode文本导出到GBK文本。


2.3 互联网的字符集

2.3.1 字符集


互联网上的信息缤纷多彩,但文本依然是最重要的信息载体。html文件通过标记表明自己使用的字符集。例如:


<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″>


或者:


<meta http-equiv=”charset” content=”iso-8859-1″>


那么我们可以使用哪些字符集(charset)呢?在IETF(互联网工程任务组)的网页上维护着一份可以在互联网上使用的字符集的清单:



CHARACTER SETS



。如果有新的字符集被登记,IETF会更新这份文档。


简单浏览一下,2006年12月7日的版本列出了253个字符集。其中也包括微软的CP1250 ~ CP1258,在这里它们不会被称作什么ANSI代码页,而是被简单地称作windows-1250、windows-1251等。其实在Unicode被广泛使用前,除了中日韩等大字符集,世界上,特别是西方使用最广泛的字符集应该是ISO 8859系列字符集。

2.3.2 ISO 8859系列字符集


ISO 8859系列字符集是欧洲计算机制造商协会(ECMA)在上世纪80年代中期设计,并被国际标准化(ISO)组织采纳为国际标准。ISO 8859系列字符集目前有15个字符集,包括:


  • ISO 8859-1 大部分的西欧语系,例如英文、法文、西班牙文和德文等(Latin-1)

  • ISO 8859-2 大部分的中欧和东欧语系,例如捷克文、波兰文和匈牙利文等(Latin-2)

  • ISO 8859-3 欧洲东南部和其它各种文字(Latin-3)

  • ISO 8859-4 斯堪的那维亚和波罗的海语系(Latin-4)

  • ISO 8859-5 拉丁文与斯拉夫文(俄文、保加利亚文等)

  • ISO 8859-6 拉丁文与阿拉伯文

  • ISO 8859-7 拉丁文与希腊文

  • ISO 8859-8 拉丁文与希伯来文

  • ISO 8859-9 为土耳其文修正的Latin-1(Latin-5)

  • ISO 8859-10 拉普人、北欧与爱斯基摩人的文字(Latin-6)

  • ISO 8859-11 拉丁文与泰文

  • ISO 8859-13 波罗的海周边语系,例如拉脱维亚文等(Latin-7)

  • ISO 8859-14 凯尔特文,例如盖尔文、威尔士文等(Latin-8)

  • ISO 8859-15 改进的Latin-1,增加遗漏的法文、芬兰文字符和欧元符号(Latin-9)

  • ISO 8859-16 罗马尼亚文(Latin-10)


其中缺少的编号12据说是为了预留给天城体梵文字母(Deva-nagari)的。印地文和尼泊尔文都使用了这种在七世纪形成的字母表。由于印度定义了自己的编码ISCII(Indian Script Code for Information Interchange),所以这个编号就未被使用。ISO 8859系列字符集都是单字节字符集,即只使用0x00-0xFF对字符编码。


大家都知道ASCII吧,那么大家知道ANSI X3.4和ISO 646吗?在1968年发布的ANSI X3.4和1972年发布的ISO 646就是ASCII编码,只不过是不同组织发布的。绝大多数字符集都与ASCII编码保持兼容,ISO 8859系列字符集也不例外,它们的0x00-0x7f都与ASCII码保持一致,各字符集的不同之处在于如何利用0x80-0xff的码位。使用UniToy可以查看ISO 8859系列所有字符集的编码,例如:




通过这些演示,大家是不是觉得代码页和字符集都是很简单、朴实的东西呢?好,在进入Unicode的话题前,让我们先看一个很深奥的概念。


浅谈文字编码和Unicode(下)


3 字符编码模型


程序员经常会面对复杂的问题,而降低复杂性的最简单的方法就是分而治之。Peter Constable在他的文章



“Character set encoding basics Understanding character set encodings and legacy encodings”



中描述了字符编码的四层模型。我觉得这种说法确实可以更清晰地展现字符编码中发生的事情,所以在这里也介绍一下。


3.1 字符的范围(Abstract character repertoire)


设计字符编码的第一层就是确定字符的范围,即要支持哪些字符。有些编码方案的字符范围是固定的,例如ASCII、ISO 8859 系列。有些编码方案的字符范围是开放的,例如Unicode的字符范围就是世界上所有的字符。


3.2 用数字表示字符(Coded character set)


设计字符编码的第二层是将字符和数字对应起来。可以将这个层次理解成数学家(即从数学角度)看到的字符编码。数学家看到的字符编码是一个正整数。例如在Unicode中:汉字“字”对应的数字是23383。汉字“

”对应的数字是134192。


在写html文件时,可以通过输入”&#23383;”来插入字符“字”。不过在设计字符编码时,我们还是习惯用16进制表示数字。即将23383写成0x5BD7,将134192写成0x20C30。


3.3 用基本数据类型表示字符(Character encoding form)


设计字符编码的第三层是用编程语言中的基本数据类型来表示字符。可以将这个层次理解成程序员看到的字符编码。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:


BYTE data_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97}; // UTF-8编码

WORD data_utf16[]={0x6c49,0x5b57};                            // UTF-16编码

DWORD data_utf32[]={0x6c49,0x5b57};                         // UTF-32编码


这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。


“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。4.2节会介绍将数字映射到UTF编码的规则。


3.4 作为字节流的字符(Character encoding scheme)


字符编码的第四层是计算机看到的字符,即在文件或内存中的字节流。例如,“字”的UTF-32编码是0x5b57,如果用little endian表示,字节流是“57 5b 00 00”。如果用big endian表示,字节流是“00 00 5b 57”。


字符编码的第三层规定了一个字符由哪些编码单位按什么顺序表示。字符编码的第四层在第三层的基础上又考虑了编码单位内部的字节序。UTF-8的编码单位是字节,不受字节序的影响。UTF-16、UTF-32根据字节序的不同,又衍生出UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE四种编码方案。LE和BE分别是Little Endian和Big Endian的缩写。


3.5 小结


通过四层模型,我们又把字符编码中发生的这些事情梳理了一遍。其实大多数代码页都不需要完整的四层模型,例如GB18030以字节为编码单位,直接规定了字节序列和字符的映射关系,跳过了第二层,也不需要第四层。


4 再谈Unicode


Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。


Unicode字符集可以简写为UCS(Unicode Character Set)。早期的Unicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。


Unicode标准计划使用group 0 的17个平面: 从BMP(平面0)到平面16,即数字0-0x10FFFF。《谈谈Unicode编码》主要介绍了BMP的编码,本文将介绍完整的Unicode编码,并从多个角度浏览Unicode。本文的介绍基于Unicode 5.0.0版本。


4.1 浏览Unicode


先看一些数字:每个平面有2^16=65536个码位。Unicode计划使用了17个平面,一共有17*65536=1114112个码位。其实,现在已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000-0xFFFFD和0x100000-0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA。


平面0也有一个专用区:0xE000-0xF8FF,有6400个码位。平面0的0xD800-0xDFFF,共2048个码位,是一个被称作代理区(Surrogate)的特殊区域。它的用途将在4.2节介绍。


238605-65534*2-6400-2408=99089。余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,它们对应着Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。


在更深入地了解Unicode字符前,我们先了解一下UCD。

4.1.1 什么是UCD


UCD是Unicode字符数据库(Unicode Character Database)的缩写。UCD由一些描述Unicode字符属性和内部关系的纯文本或html文件组成。大家可以在Unicode组织的网站看到UCD的



最新版本





UCD中的文本文件大都是适合于程序分析的Unicode相关数据。其中的html文件解释了数据库的组织,数据的格式和含义。UCD中最庞大的文件无疑就是描述汉字属性的文件Unihan.txt。在UCD 5.0,0中,Unihan.txt文件大小有28,221K字节。Unihan.txt中包含了很多有参考价值的索引,例如汉字部首、笔划、拼音、使用频度、四角号码排序等。这些索引都是基于一些比较权威的辞典,但大多数索引只能检索部分汉字。


我介绍UCD的目的主要是为了使用其中的两个概念:Block和Script。

4.1.2 Block


UCD中的Blocks.txt将Unicode的码位分割成一些连续的Block,并描述了每个Block的用途:


开始码位

结束码位

Block名称(英文)

Block名称(中文)

0000

007F

Basic Latin

基本拉丁字母

0080

00FF

Latin-1 Supplement

拉丁字母补充-1

0100

017F

Latin Extended-A

拉丁字母扩充-A

0180

024F

Latin Extended-B

拉丁字母扩充-B

0250

02AF

IPA Extensions

国际音标扩充

02B0

02FF

Spacing Modifier Letters

进格修饰字符

0300

036F

Combining Diacritical Marks

组合附加符号

0370

03FF

Greek and Coptic

希腊文和哥普特文

0400

04FF

Cyrillic

西里尔文

0500

052F

Cyrillic Supplement

西里尔文补充

0530

058F

Armenian

亚美尼亚文

0590

05FF

Hebrew

希伯来文

0600

06FF

Arabic

基本阿拉伯文

0700

074F

Syriac

叙利亚文

0750

077F

Arabic Supplement

阿拉伯文补充

0780

07BF

Thaana

塔纳文

07C0

07FF

NKo

N’Ko字母表

0900

097F

Devanagari

天成文书(梵文)

0980

09FF

Bengali

孟加拉文

0A00

0A7F

Gurmukhi

锡克教文

0A80

0AFF

Gujarati

古吉拉特文

0B00

0B7F

Oriya

奥里亚文

0B80

0BFF

Tamil

泰米尔文

0C00

0C7F

Telugu

泰卢固文

0C80

0CFF

Kannada

卡纳达文

0D00

0D7F

Malayalam

德拉维族文

0D80

0DFF

Sinhala

僧伽罗文

0E00

0E7F

Thai

泰文

0E80

0EFF

Lao

老挝文

0F00

0FFF

Tibetan

藏文

1000

109F

Myanmar

缅甸文

10A0

10FF

Georgian

格鲁吉亚文

1100

11FF

Hangul Jamo

朝鲜文

1200

137F

Ethiopic

埃塞俄比亚文

1380

139F

Ethiopic Supplement

埃塞俄比亚文补充

13A0

13FF

Cherokee

切罗基文

1400

167F

Unified Canadian Aboriginal Syllabics

加拿大印第安方言

1680

169F

Ogham

欧甘文

16A0

16FF

Runic

北欧古字

1700

171F

Tagalog

塔加路文

1720

173F

Hanunoo

哈努诺文

1740

175F

Buhid

布迪文

1760

177F

Tagbanwa

Tagbanwa文

1780

17FF

Khmer

高棉文

1800

18AF

Mongolian

蒙古文

1900

194F

Limbu

林布文

1950

197F

Tai Le

德宏傣文

1980

19DF

New Tai Lue

新傣文

19E0

19FF

Khmer Symbols

高棉文

1A00

1A1F

Buginese

布吉文

1B00

1B7F

Balinese

巴厘文

1D00

1D7F

Phonetic Extensions

拉丁字母音标扩充

1D80

1DBF

Phonetic Extensions Supplement

拉丁字母音标扩充增补

1DC0

1DFF

Combining Diacritical Marks Supplement

组合附加符号补充

1E00

1EFF

Latin Extended Additional

拉丁字母扩充附加

1F00

1FFF

Greek Extended

希腊文扩充

2000

206F

General Punctuation

一般标点符号

2070

209F

Superscripts and Subscripts

上标和下标

20A0

20CF

Currency Symbols

货币符号

20D0

20FF

Combining Diacritical Marks for Symbols

符号用组合附加符号

2100

214F

Letterlike Symbols

似字母符号

2150

218F

Number Forms

数字形式

2190

21FF

Arrows

箭头符号

2200

22FF

Mathematical Operators

数学运算符号

2300

23FF

Miscellaneous Technical

零杂技术用符号

2400

243F

Control Pictures

控制图符

2440

245F

Optical Character Recognition

光学字符识别

2460

24FF

Enclosed Alphanumerics

带括号的字母数字

2500

257F

Box Drawing

制表符

2580

259F

Block Elements

方块元素

25A0

25FF

Geometric Shapes

几何形状

2600

26FF

Miscellaneous Symbols

零杂符号

2700

27BF

Dingbats

杂锦字型

27C0

27EF

Miscellaneous Mathematical Symbols-A

零杂数学符号-A

27F0

27FF

Supplemental Arrows-A

箭头符号补充-A

2800

28FF

Braille Patterns

盲文

2900

297F

Supplemental Arrows-B

箭头符号补充-B

2980

29FF

Miscellaneous Mathematical Symbols-B

零杂数学符号-B

2A00

2AFF

Supplemental Mathematical Operators

数学运算符号

2B00

2BFF

Miscellaneous Symbols and Arrows

零杂符号和箭头

2C00

2C5F

Glagolitic

格拉哥里字母表

2C60

2C7F

Latin Extended-C

拉丁字母扩充-C

2C80

2CFF

Coptic

科普特文

2D00

2D2F

Georgian Supplement

格鲁吉亚文补充

2D30

2D7F

Tifinagh

提非纳字母

2D80

2DDF

Ethiopic Extended

埃塞俄比亚文扩充

2E00

2E7F

Supplemental Punctuation

标点符号补充

2E80

2EFF

CJK Radicals Supplement

中日韩部首补充

2F00

2FDF

Kangxi Radicals

康熙字典部首

2FF0

2FFF

Ideographic Description Characters

汉字结构描述字符

3000

303F

CJK Symbols and Punctuation

中日韩符号和标点

3040

309F

Hiragana

平假名

30A0

30FF

Katakana

片假名

3100

312F

Bopomofo

注音符号

3130

318F

Hangul Compatibility Jamo

朝鲜文兼容字母

3190

319F

Kanbun

日文的汉字批注

31A0

31BF

Bopomofo Extended

注音符号扩充

31C0

31EF

CJK Strokes

中日韩笔划

31F0

31FF

Katakana Phonetic Extensions

片假名音标扩充

3200

32FF

Enclosed CJK Letters and Months

带括号的中日韩字母及月份

3300

33FF

CJK Compatibility

中日韩兼容字符

3400

4DBF

CJK Unified Ideographs Extension A

中日韩统一表意文字扩充A

4DC0

4DFF

Yijing Hexagram Symbols

易经六十四卦象

4E00

9FFF

CJK Unified Ideographs

中日韩统一表意文字

A000

A48F

Yi Syllables

彝文音节

A490

A4CF

Yi Radicals

彝文字根

A700

A71F

Modifier Tone Letters

声调修饰字母

A720

A7FF

Latin Extended-D

拉丁字母扩充-D

A800

A82F

Syloti Nagri

Syloti Nagri字母表

A840

A87F

Phags-pa

Phags-pa字母表

AC00

D7AF

Hangul Syllables

朝鲜文音节

D800

DB7F

High Surrogates

高位替代

DB80

DBFF

High Private Use Surrogates

高位专用替代

DC00

DFFF

Low Surrogates

低位替代

E000

F8FF

Private Use Area

专用区

F900

FAFF

CJK Compatibility Ideographs

中日韩兼容表意文字

FB00

FB4F

Alphabetic Presentation Forms

字母变体显现形式

FB50

FDFF

Arabic Presentation Forms-A

阿拉伯文变体显现形式-A

FE00

FE0F

Variation Selectors

字型变换选取器

FE10

FE1F

Vertical Forms

竖排标点符号

FE20

FE2F

Combining Half Marks

组合半角标示

FE30

FE4F

CJK Compatibility Forms

中日韩兼容形式

FE50

FE6F

Small Form Variants

小型变体形式

FE70

FEFF

Arabic Presentation Forms-B

阿拉伯文变体显现形式-B

FF00

FFEF

Halfwidth and Fullwidth Forms

半角及全角字符

FFF0

FFFF

Specials

特殊区域

10000

1007F

Linear B Syllabary

线形文字B音节文字

10080

100FF

Linear B Ideograms

线形文字B表意文字

10100

1013F

Aegean Numbers

爱琴海数字

10140

1018F

Ancient Greek Numbers

古希腊数字

10300

1032F

Old Italic

古意大利文

10330

1034F

Gothic

哥特文

10380

1039F

Ugaritic

乌加里特楔形文字

103A0

103DF

Old Persian

古波斯文

10400

1044F

Deseret

德塞雷特大学音标

10450

1047F

Shavian

肃伯纳速记符号

10480

104AF

Osmanya

Osmanya字母表

10800

1083F

Cypriot Syllabary

塞浦路斯音节文字

10900

1091F

Phoenician

腓尼基文

10A00

10A5F

Kharoshthi

迦娄士悌文

12000

123FF

Cuneiform

楔形文字

12400

1247F

Cuneiform Numbers and Punctuation

楔形文字数字和标点

1D000

1D0FF

Byzantine Musical Symbols

东正教音乐符号

1D100

1D1FF

Musical Symbols

音乐符号

1D200

1D24F

Ancient Greek Musical Notation

古希腊音乐符号

1D300

1D35F

Tai Xuan Jing Symbols

太玄经符号

1D360

1D37F

Counting Rod Numerals

算筹

1D400

1D7FF

Mathematical Alphanumeric Symbols

数学用字母数字符号

20000

2A6DF

CJK Unified Ideographs Extension B

中日韩统一表意文字扩充 B

2F800

2FA1F

CJK Compatibility Ideographs Supplement

中日韩兼容表意文字补充

E0000

E007F

Tags

标签

E0100

E01EF

Variation Selectors Supplement

字型变换选取器补充

F0000

FFFFF

Supplementary Private Use Area-A

补充专用区-A

100000

10FFFF

Supplementary Private Use Area-B

补充专用区-B


Block是Unicode字符的一个属性。属于同一个Block的字符有着相近的用途。Block表中的开始码位、结束码位只是用来划分出一块区域,在开始码位和结束码位之间可能还有很多未定义的码位。使用UniToy,大家可以按照Block浏览Unicode字符,既可以按列表显示:





也可以显示每个字符的详细信息:



4.1.3 Script


Unicode中每个字符都有一个Script属性,这个属性表明字符所属的文字系统。Unicode目前支持以下Script:


Script名称(英文)

Script名称(中文)

Script包含的字符数

Arabic

阿拉伯文

966

Armenian

亚美尼亚文

90

Balinese

巴厘文

121

Bengali

孟加拉文

91

Bopomofo

汉语注音符号

64

Braille

盲文

256

Buginese

布吉文

30

Buhid

布迪文

20

Canadian Aboriginal

加拿大印第安方言

630

Cherokee

切罗基文

85

Common

Common

5020

Coptic

科普特文

128

Cuneiform

楔形文字

982

Cypriot

塞浦路斯音节文字

55

Cyrillic

西里尔文

277

Deseret

德塞雷特大学音标

80

Devanagari

天成文书(梵文)

107

Ethiopic

埃塞俄比亚文

461

Georgian

格鲁吉亚文

120

Gothic

哥特文

94

Glagolitic

格拉哥里字母表

27

Greek

希腊文

506

Gujarati

古吉拉特文

83

Gurmukhi

锡克教文

77

Han

汉文

71570

Hangul

韩文书写系统

11619

Hanunoo

哈努诺文

21

Hebrew

希伯来文

133

Hiragana

平假名

89

Inherited

Inherited

461

Kannada

卡纳达文

86

Katakana

片假名

164

Kharoshthi

迦娄士悌文

65

Khmer

高棉文

146

Lao

老挝文

65

Latin

拉丁文系

1070

Limbu

林布文(尼泊尔东部)

66

Linear B

线形文字B

211

Malayalam

德拉维族文(印度)

78

Mongolian

蒙古文

152

Myanmar

缅甸文

78

New Tai Lue

新傣文

80

Nko

N’Ko字母表

59

Ogham

欧甘文字

29

Old Italic

古意大利文

35

Old Persian

古波斯文

50

Oriya

奥里亚文

81

Osmanya

Osmanya字母表

40

Phags Pa

Phags Pa字母表(蒙古)

56

Phoenician

腓尼基文

27

Runic

古代北欧文

78

Shavian

肃伯纳速记符号

48

Sinhala

僧伽罗文

80

Syloti Nagri

Syloti Nagri字母表(印度)

44

Syriac

叙利亚文

77

Tagalog

塔加路文(菲律宾)

20

Tagbanwa

Tagbanwa文(菲律宾)

18

Tai Le

德宏傣文

35

Tamil

泰米尔文

71

Telugu

泰卢固文(印度)

80

Thaana

马尔代夫书写体

50

Thai

泰国文

86

Tibetan

藏文

195

Tifinagh

提非纳字母表

55

Ugaritic

乌加里特楔形文字

31

Yi

彝文

1220


其中,有两个Script值有着特殊的含义:


  • Common:Script属性为Common的字符可能在多个文字系统中使用,不是某个文字系统特有的。例如:空格、数字等。

  • Inherited:Script属性为Inherited的字符会继承前一个字符的Script属性。主要是一些组合用符号,例如:在“组合附加符号”区(0x300-0x36f),字符的Script属性都是Inherited。


UCD中的Script.txt列出了每个字符的Script属性。使用UniToy可以按照Script属性查看字符。例如:




左侧Script窗口中,第一层节点是按英文字母顺序排列的Script属性。第二层节点是包含该Script文字的行(row),点击后显示该行内属于这个Script的字符。这样,就可以集中查看属于同一文字系统的字符。

4.1.4 Unicode中的汉字


前面提过,在Unicode已定义的99089个字符中,有71226个字符是汉字。它们的分布如下:


Block名称

开始码位

结束码位

数量

中日韩统一表意文字扩充A

3400

4db5

6582


中日韩统一表意文字

4e00

9fbb

20924


中日韩兼容表意文字

f900

fa2d

302


中日韩兼容表意文字

fa30

fa6a

59


中日韩兼容表意文字

fa70

fad9

106

中日韩统一表意文字扩充B

20000

2a6d6

42711


中日韩兼容表意文字补充

2f800

2fa1d

542


UCD的Unihan.txt中的部首偏旁索引(kRSUnicode)可以检索全部71226个汉字。kRSUnicode的部首是按照康熙字典定义的,共214个部首。简体字按照简体部首对应的繁体部首检索。UniToy整理了康熙字典部首对应的简体部首,提供了按照部首检索汉字的功能:




4.2 UTF编码


在字符编码的四个层次中,第一层的范围和第二层的编码在4.1节已经详细讨论过了。本节讨论第三层的UTF编码和第四层的字节序,主要谈谈第三层的UTF编码,即怎样将Unicode定义的编码转换成程序数据。

4.2.1 UTF-8


UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:


Unicode编码(16进制)

UTF-8 字节流(二进制)

000000 – 00007F

0xxxxxxx

000080 – 0007FF

110xxxxx 10xxxxxx

000800 – 00FFFF

1110xxxx 10xxxxxx 10xxxxxx

010000 – 10FFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx


UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。


例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:

1110

xxxx

10

xxxxxx

10

xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:

1110

0110

10

110001

10

001001,即E6 B1 89。


例2:“

”字的Unicode编码是0x20C30。0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:

11110

xxx

10

xxxxxx

10

xxxxxx

10

xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:

11110

000

10

100000

10

110000

10

110000,即F0 A0 B0 B0。

4.2.2 UTF-16


UniToy有个“输出编码”功能,可以输出当前选择的文本编码。因为UniToy内部采用UTF-16编码,所以输出的编码就是文本的UTF-16编码。例如:如果我们输出“汉”字的UTF-16编码,可以看到0x6C49,这与“汉”字的Unicode编码是一致的。如果我们输出“

”字的UTF-16编码,可以看到0xD843, 0xDC30。“

”字的Unicode编码是0x20C30,它的UTF-16编码是怎样得到的呢?


4.2.2.1 编码规则


UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:


  • 如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。

  • 如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:

    110110

    yyyyyyyyyy

    110111

    xxxxxxxxxx。


为什么U’可以被写成20个二进制位?Unicode的最大码位是0x10ffff,减去0x10000后,U’的最大值是0xfffff,所以肯定可以用20个二进制位表示。例如:“

”字的Unicode编码是0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:

110110

0001000011

110111

0000110000,即0xD843 0xDC30。


4.2.2.2 代理区(Surrogate)


按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是

110110

00 00000000到

110110

11 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是

110111

00 00000000到

110111

11 11111111,即0xDC00-0xDFFF。


为了将一个WORD的UTF-16编码与两个WORD的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate):


D800

DB7F

High Surrogates

高位替代

DB80

DBFF

High Private Use Surrogates

高位专用替代

DC00

DFFF

Low Surrogates

低位替代


高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。那么,高位专用替代是什么意思?我们来解答这个问题,顺便看看怎么由UTF-16编码推导Unicode编码。


解:如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:



110110

1110000000

110111

00 00000000 –

110110

1111111111

110111

1111111111


按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到


1110 0000 0000 0000 0000 – 1111 1111 1111 1111 1111


即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。

4.2.3 UTF-32


UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

4.2.4 字节序


根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。例如:


字符

Unicode编码

UTF-16LE

UTF-16BE

UTF32-LE

UTF32-BE



0x6C49

49 6C

6C 49

49 6C 00 00

00 00 6C 49



0x20C30

43 D8 30 DC

D8 43 DC 30

30 0C 02 00

00 02 0C 30



那么,怎么判断字节流的字节序呢?


Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符”零宽无中断空格”。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应该出现在实际传输中。下表是各种UTF编码的BOM:


UTF编码

Byte Order Mark

UTF-8

EF BB BF

UTF-16LE

FF FE

UTF-16BE

FE FF

UTF-32LE

FF FE 00 00

UTF-32BE

00 00 FE FF


5 结束语


程序员的工作就是将复杂的世界简单地表达出来,希望这篇文章也能做到这一点。本文的初稿完成于2007年2月14日。我会在我的个人主页



http://www.fmddlmyy.cn



维护这篇文章的最新版本。

<script language=”javascript” type=”text/javascript”>
</script>