关闭

gbk, gb2312,big5,unicode,utf-8,utf-16 GBK、GB18030与Unicode的映射

1182人阅读 评论(0) 收藏 举报
gbk, gb2312,big5,unicode,utf-8,utf-16的区别
2009-10-23 21:46

这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题: 

问题一: 
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢? 

我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢? 

问题二: 
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。 
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。 

0、big endian和little endian 
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。 

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。 

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。 

1、字符编码、内码,顺带介绍汉字编码 
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。 

GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 

GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。 

从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。 

有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。 

这里还有一些细节: 

GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。 

在DBCS中,GB内码的存储格式始终是big endian,即高位在前。 

GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。 

2、Unicode、UCS和UTF 
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。 

Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。 

根据维基百科全书(
http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 

在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/)。

GB18030编码研究以及GBK、GB18030与Unicode的映射

GB18030有两个版本:GB18030-2000和GB18030-2005。在本文中,没有指明版本的GB18030是指GB18030-2005。本文讨论了以下问题:

  1. GB2312有682个图形符号,都放在1区。GBK的1区有717个图形符号,5区有166个图形符号,一共有883个图形符号。GB18030的1区有728个图形符号,5区还是166个符号。那么,GBK的1区在GB2312基础上增加了哪35个符号?GB18030又增加了哪些符号?
  2. GBK支持21003个汉字与883个图形符号,一共21886个字符。这21886个字符究竟是哪些字符?这21886个字符的编码在GB18030中有什么变化?
  3. GB18030是怎样映射Unicode的全部0x110000个码位的?
  4. GB18030-2000和GB18030-2005在字汇上有什么区别,在编码上有什么区别?
  5. GB18030-2005的双字节区中有2067个码位被映射到Unicode BMP的PUA。这些码位有什么规律?这些码位中定义了多少字符?其实这2067个码位中只定义了24个字符。
  6. GBK的21886个字符中有95个字符被映射到Unicode BMP的PUA。在GB18030中这95个字符的编码有哪些变化?哪些字符保持了原来的编码?
  7. GBK的23940个码位中有多少码位被映射到Unicode BMP的PUA?在GB18030中这些码位的编码有什么变化?

在讨论这些问题前,我们先约定一下码位空间的表示方法。

0 码位空间

0.1 约定

GBK是双字节编码,每个字符用两个字节表示。GB18030是多字节字符集,它的字符可以用一个、两个或四个字节表示。码位空间由各字节的范围确定。例如:GB18030的四字节字符码位空间是:

  • 第一字节在0x81~0xFE之间
  • 第二字节在0x30~0x39之间
  • 第三字节在0x81~0xFE之间
  • 第四字节在0x30~0x39之间

为了表述方便,我们用0x81308130~0xFE39FE39表示这个码位空间。也就是说:在本文中0x81308130~0xFE39FE39所指的并是从0x81308130到0xFE39FE39的连续2097773834(0xFE39FE39-0x81308130+1)个字节。在本文中,0x81308130~0xFE39FE39所指的是编码的各字节在对应范围内的码位空间,这个码位空间的码位数目是:

(0xFE-0x81+1)*(0x39-0x30+1)*(0xFE-0x81+1)*(0x39-0x30+1)=126*10*126*10=1587600

同理,0xB0A1~0xF7FE代表的码位空间是第一字节在0xB0~0xF7之间,第二字节在0xA1~0xFE之间的所有码位。这个码位空间的码位数目是:

(0xF7-0xB0+1)*(0xFE-0xA1+1)=72*94=6768

这个码位空间就是GBK和GB18030的2区,在这6768个码位中定义了6763个字符。

本文用~表示上述码位空间,用-表示一般的范围,即:

  • 0xA1A1~0xA9FE 表示第一字节在0xA1到0xA9之间,第二字节在0xA1~0xFE之间的846((0xA9-0xA1+1)*(0xFE-0xA1+1)=9*94)个码位。
  • 0xE000-0xF8FF 表示从0xE000-0xF8FF的连续6400(0xF8FF-0xE000+1)个码位。

0.2 习题

读者如果已经理解了上面的约定,请完成下面两个习题:

  1. 习题一:求码位空间0x8140~0xFE7E的码位数目。
  2. 习题二:求码位空间0x8180~0xFEFE的码位数目。

0.3 答案

以下是习题0.2的答案:

  1. 习题一:(0xFE-0x81+1)*(0x7E-0x40+1)=126*63=7938
  2. 习题二:(0xFE-0x81+1)*(0xFE-0x80+1)=126*127=16002

GB18030双字节字符的码位空间就是0x8140~0xFE7E和0x8180~0xFEFE,双字节字符的码位数目是7938+16002=23940。0x8140~0xFE7E和0x8180~0xFEFE也是GBK的全部码位空间。GBK在这23940个码位中定义了21886个字符。

1 GBK回顾

1.1 简介

GBK是双字节编码方案。它的码位空间就是前面所说的0x8140~0xFE7E和0x8180~0xFEFE,一共23940个码位。在这23940个码位上定义了21886个字符,包括21003个汉字和883个图形符号。《Unicode、GB2312、GBK和GB18030中的汉字》详细讨论了这21003个汉字。本文的第3节会讨论GB2312、GBK和GB18030的图形符号。

GBK的码位空间可以划分为以下区域:

类别 区名 码位范围 码位数 字符数
符号区 1区 0xA1A1~0xA9FE 846 717
5区 0xA840~0xA97E和0xA880~0xA9A0 192 166
汉字区 2区 0xB0A1~0xF7FE 6768 6763
3区 0x8140~0xA07E和0x8180~0xA0FE 6080 6080
4区 0xAA40~0xFE7E和0xAA80~0xFEA0 8160 8160
用户自定义区 用户区1 0xAAA1~0xAFFE 564  
用户区2 0xF8A1~0xFEFE 658  
用户区3 0xA140~0xA77E和0xA180~0xA7A0 672  

1.2 GBK字符与Unicode的映射

我制作了一个Excel文件:附件1。这个文件包含3张表格:

  1. 按照GBK编码排序的GBK全部21886字符码表。这个表格有3列:字符、GBK编码、Unicode编码。
  2. 按照Unicode编码排序的GBK全部21886字符码表。这个表格有3列:字符、Unicode编码、GBK编码。
  3. 从按Unicode编码排序的表格中,很容易找到被映射到PUA(0xE000-0xF8FF)的字符。GBK的21886个字符中有95个字符属于PUA。第三张表格列出了这95个字符(A列)的GBK编码(B列)、Unicode编码(C列)以及这些字符在GB18030中对应的Unicode编码(D列)。
    其中D列可能不太容易理解,我再解释一下。GB18030是兼容GBK的,所以这些字符的GBK编码和GB18030编码是相同的。例如 的GBK编码和GB18030编码都是0xA8BF。但是在GBK和GB18030中, 被映射到不同的Unicode码位。在GBK中,0xA8BF被映射到Unicode的0xE7C8。在Unicode中,码位0xE7C8是一个PUA码位,保留给用户使用。在GB18030中,0xA8BF被映射到Unicode的0x01F9。在Unicode中,码位0x01F9属于“拉丁字母扩充-B”这个Block,这个码位定义的字符是“带抑音符的拉丁文小写字母 N”,字形就是 。

1.3 GBK码位与Unicode的映射

GBK的23940个码位定义了21886个字符,还有23940-21886=2054个空闲码位,这2054个码位都被映射到Unicode的PUA。在设计GBK时,GBK的21886个字符中有95个在Unicode中没有对应字符,所以这95个字符也被映射到Unicode的PUA。在GBK的23940个码位中,一共有2054+95=2149个码位被映射到PUA,对应的PUA编码是0xE000-0xE864。0xE000-0xE864就是2149个码位。这2149个码位的分配有以下规律:

码位所在区域 码位数量 映射到的PUA范围
用户区1:0xAAA1~0xAFFE 564 0xE000-0xE233
用户区2:0xF8A1~0xFEFE 658 0xE234-0xE4C5
用户区3:0xA140~0xA77E和A180-A7A0 672 0xE4C6-0xE765
符号区(1区和5区)的170个空闲码位 170 0xE766-0xE80F
2区的5个空闲码位:0xD7FA-0xD7FE 5 0xE810-0xE814
4区的80个Unicode当时没有定义的字符:FE50-FE7E和FE80-FEA0 80 0xE815-0xE864

附件2包含两张表格:

  1. 23940个GBK码位与Unicode的映射。两组数据分别按GBK和Unicode排序。
  2. 2149个映射到PUA的码位,按Unicode顺序排列。

2 GB18030编码

2.1 概述

GB18030是多字节字符集,它的字符可以用一个、两个或四个字节表示。GB18030的码位定义如下:

字节数 码位空间 码位数 字符数
单字节 0x00~0x7F 128 128
双字节 0x8140~0xFE7E和0x8180~0xFEFE 23940 21897
四字节 0x81308130~0xFE39FE39 1587600 54531

GB18030有128+23940+1587600=1611668个码位。Unicode的码位数目是0x110000(1114112),少于GB18030。所以,GB18030有足够的空间映射Unicode的所有码位。

GB18030的1611668个码位目前定义了128+21897+54531=76556个字符。Unicode 5.0定义了99089个字符。

2.2 设计思路

GB18030编码可以分为:单字节部分、双字节部分和四字节部分。单字节部分与Unicode的0x00-0x7f完全相同。双字节部分与GBK有两点差异:

  1. 在1区增加了11个字符。这样1区就有717+11=728个字符。增加的11个字符是:一个欧元符号(0xA2E3)和10个竖排标点符号(0xA6D9-0xA6DF、0xA6EC-0xA6ED和0xA6F3)。
  2. 原来因为Unicode没有收录而映射到PUA的字符中的部分字符被新版本的Unicode收录,所以将这些字符映射到非PUA的码位。

Unicode的BMP一共有65536个码位。其中代理区(0xD800-0xDFFF)有2048个码位,这2048个码位是不能定义字符的。GB18030的单字节部分映射了128个码位,GB18030的双字节部分映射了23940个码位。还剩下65536-2048-128-23940=39420个码位。

GB18030将这39420个码位顺序映射到从0x81308130开始的码位空间。GB18030将Unicode的16个辅助平面(0x10000-0x10FFFF,一共1048576个码位)顺序映射到从0x90308130开始的码位空间。GB18030四字节部分中只有这两个区域定义了字符,其它空间都是保留区和自定义区。本文的第3节和第4节还会详细讨论GB18030的双字节和四字节部分。

GB18030的设计思路可以概括到以下几点:

  1. 单字节部分与Unicode一致。
  2. 双字节部分与GBK兼容。适当调整一些字符与Unicode的映射。这些字符原来因为Unicode没有收录而被映射到PUA,现在因为Unicode已经收录而调整到非PUA的Unicode码位。
  3. 将Unicode BMP部分还没有映射的39420个码位顺序映射到从0x81308130开始的四字节部分。
  4. 将Unicode BMP以外的16个辅助平面映射到39420个码位顺序映射到从0x90308130开始的四字节部分。

在GB18030目前定义的76556个字符中,只有24个字符被定义到Unicode的PUA区。这24个字符包括1区的10个竖排标点符号(0xA6D9-0xA6DF、0xA6EC-0xA6ED和0xA6F3)和4区的14个汉字(0xFE51、0xFE52、0xFE53、0xFE59、0xFE61、0xFE66、0xFE67、0xFE6C、0xFE6D、0xFE76、0xFE7E、0xFE90、0xFE91、0xFEA0)。4区的14个汉字在Unicode 5.0中其实也可以找到非PUA的编码,详见《Unicode、GB2312、GBK和GB18030中的汉字》。但按照GB18030,它们还是应该映射到PUA码位。

2.3 GB18030-2000和GB18030-2005的区别及以后版本

GB18030-2005与GB18030-2000的编码体系结构是完全相同的。GB18030-2005相对于GB18030-2000主要有以下变化:

  1. 在四字节字符表中增加CJK统一汉字扩充B和已经在GB13000中编码的我国少数民族文字字符的字形。其实GB18030-2000已经映射了这些码位,但GB18030-2000没有给出这些字符的字形。
  2. 调整字符 的编码。

其中 的编码调整比较有意思。 的GB18030编码是0xA8BC, 在Unicode 5.0的编码是0x1E3F。在GB18030-2000中0xA8BC被映射到Unicode的0xE7C7,因为双字节部分没有映射0x1E3F,所以它作为BMP的未映射字符被放到四字节部分的0x8135F437。GB18030-2005将0xA8BC映射到0x1E3F,那么Unicode码位0xE7C7怎么办呢?为了最小化对原来编码的影响,设计者将Unicode码位0xE7C7映射到本来映射0x1E3F的0x8135F437。

GB18030已经映射了Unicode的所有码位,所以不管Unicode怎么变化,GB18030不过就是在现在的码位上增加一些字形而已,编码不会变化。只有现在还映射到PUA的24个字符以后可能会调整到非PUA码位。调整方法应该与 的调整方法相同。

2.4 GB18030双字节部分

前面已经介绍过GB18030双字节部分与GBK的区别,本小节再提一些细节。前面也说过,GB18030映射了Unicode除代理区外的所有码位。所以,Unicode BMP的6400个PUA码位在GB18030中都有对应的码位。GB18030双字节部分映射了2067个PUA码位。

前面说过,GBK映射了2149个PUA码位。现在GB18030双字节部分映射了2067个PUA码位。所以有2149-2067=82个字符的映射发生了变化。GBK原来有95个字符映射到PUA,其中81个字符在GB18030中被映射到非PUA码位。余下的14个汉字就是《Unicode、GB2312、GBK和GB18030中的汉字》提到的那14个汉字(0xFE51、0xFE52、0xFE53、0xFE59、0xFE61、0xFE66、0xFE67、0xFE6C、0xFE6D、0xFE76、0xFE7E、0xFE90、0xFE91、0xFEA0)。附件1列出了这些字符的编码变化。82个映射变化的码位,除了这81个外,还有一个就是欧元符号:GB18030编码是0xA2E3,Unicode编码是0x20AC。码位0xA2E3在GBK中被映射到0xE76C,GBK的码位0xA2E3没有定义字符。

GB18030双字节部分与Unicode的映射没有规律,只能通过查表方法映射。

2.5 GB18030四字节部分

GB18030四字节部分的字符可以见GB18030-2005的“表3 四字节部分的码位安排”,一共54531个字符。GB18030四字节部分的码位可以见GB18030-2005的“7.3 四字节部分字符的排列顺序”。其中定义字符的只有两个区域:

  • GB18030用码位0x81308130~0x8439FE39共50400个码位映射该标准单字节和双字节部分没有映射过的39420个Unicode BMP码位。
  • GB18030用码位0x90308130~0xE339FE39共1058400个码位映射Unicode 16个辅助平面(平面1到平面16)的65536*16=1048576个码位。

为了叙述方便,本文将0x81308130~0x8439FE39称作“BMP扩展部分”,将0x90308130~0xE339FE39称作“辅助平面部分”。GB18030四字节部分的码位空间是0x81308130~0xFE39FE39。第二字节有(0x39-0x30+1)=10个可能值。第三字节有(0xFE-0x81+1)=126个可能值。第四字节也是(0x39-0x30+1)=10个可能值。为了方便下面的演算,本文为这个码位空间定义几个名词:

  • 我们将四字节码位空间中第一字节相同的区域称作一级区。每个一级区有12600个码位,即:10*126*10。
  • 我们将四字节码位空间中第一字节和第二字节相同的区域称作二级区。每个二级区有1260个码位,即:126*10。
  • 我们将四字节码位空间中前三个字节相同的区域称作三级区,每个三级区有10个码位。

四字节部分一共有(0xFE-0x81+1)=126个一级区。BMP扩展部分有4个一级区。辅助平面部分有84个一级区。还有38个一级区是保留区或自定义区。

2.5.1 BMP扩展部分

BMP扩展部分占据四字节部分开头的4个一级区,一共有4*12600=50400个码位。这段空间的Unicode映射说起来还是很简单的,就是顺序映射单字节、双字节没有映射过的BMP码位。这些映射关系在GB18030-2000中确定下来。以后的调整(例如 )只是个别字符,不会影响其它字符的位置。但是因为双字节字符已经映射过的BMP码位没有什么规律,所以造成BMP扩展部分的Unicode映射也不能用公式换算,还是要查表解决。

显然这50400个码位中只用到了39420个码位,其余码位都是保留的。出于好玩,我们来计算一下最后一个非保留码位(0xFFFF)的位置,计算过程如下:

  • m1=(39420-1)/12600=3
  • n1=(39420-1)%12600=1619
  • m2=n1/1260=1619/1260=1
  • n2=n1%1260=1619%1260=359
  • m3=n2/10=359/10=35
  • n3=n2%10=359%10=9
  • 第一字节的位置是:0x81+m1=0x81+3=0x84
  • 第二字节的位置是:0x30+m2=0x30+1=0x31
  • 第三字节的位置是:0x81+m3=0x81+35=0xA4
  • 第四字节的位置是:0x30+n3=0x30+9=0x39

所以Unicode编码0xFFFF映射的GB18030码位是0x8431A439。在BMP扩展部分中,0x8431A439以后的码位都是保留码位。上述计算中,/表示整除(例如5/3=1),%表示取余(例如5%3=2)。

2.5.2 辅助平面部分

辅助平面部分用84个一级区(0x90308130~0xE339FE39)直接映射Unicode的16个辅助平面。这部分映射是可以直接用公式计算的。让我们看看怎么计算。

  • 从Unicode编码到GB18030编码的映射方法如下:
    • U=Unicode编码-0x10000
    • m1=U/12600
    • n1=U%12600
    • m2=n1/1260
    • n2=n1%1260
    • m3=n2/10
    • n3=n2%10
    • 第一字节b1=m1+0x90
    • 第二字节b2=m2+0x30
    • 第三字节b3=m3+0x81
    • 第四字节b4=n3+0x30
    按照上述方法可以计算出0x10FFFF被映射到0xE3329A35。在辅助平面部分,0xE3329A35以后的码位都是保留码位。以上所写的算法可以很容易写成C/C++代码。对于不会编程的读者,也可以用Excel公式计算。假设Unicode编码放在单元格A12,计算方法如下:
    • 将m1放在B12,B12=INT((HEX2DEC(A12)-65536)/12600)
    • 将n1放在C12,C12=MOD((HEX2DEC(A12)-65536),12600)
    • 将m2放在D12,D12=INT(C12/1260)
    • 将n2放在E12,E12=MOD(C12,1260)
    • 将m3放在F12,F12=INT(E12/10)
    • 将n3放在G12,G12=MOD(E12,10)
    • 将第一字节放在H12,H12=DEC2HEX(B12+144)
    • 将第二字节放在I12,I12=DEC2HEX(D12+48)
    • 将第三字节放在J12,J12=DEC2HEX(F12+129)
    • 将第四字节放在K12,K12=DEC2HEX(G12+48)
    附件3中有写好上述公式的Excel表格。使用函数HEX2DEC/DEC2HEX需要通过“工具->加载宏”钩上“分析工具库”。
  • 从GB18030编码到Unicode编码的映射方法如下:
    • 设GB18030编码的四个字节依次为:b1、b2、b3、b4,则
      Unicode编码=0x10000+(b1-0x90)*12600+(b2-0x30)*1260+(b3-0x81)*10+b4-0x30
    假设b1、b2、b3、b4分别放在A4、B4、C4、D4,Unicode编码放在E4,则Excel计算公式为:
    • E4 = =DEC2HEX((HEX2DEC(A4)-144)*12600+(HEX2DEC(B4)-48)*1260+(HEX2DEC(C4)-129)*10+(HEX2DEC(D4)-48)+65536)

2.6 GB18030和Unicode的映射表

附件3给出了GB18030和Unicode的映射表。这个Excel文件是在网友谢振斌先生的映射表基础上制作的,包含3张表格:

  1. 双字节部分23940个码位与Unicode的映射。两组数据分别按GB18030和Unicode排序。
  2. BMP扩展部分39420个码位与Unicode的映射。两组数据分别按GB18030和Unicode排序。
  3. 辅助平面部分,GB18030编码和Unicode编码的映射公式。

3 GB2312、GBK和GB18030中的图形符号

在研究GB18030编码的过程中,我整理了GB2312、GBK和GB18030在1区和5区的图形符号,制作了附件4。这个Excel文件包含3张表格:

  1. GB2312的1区字符表。GBK和GB18030的1区、5区字符表。用不同颜色标注了GBK增加的35个字符和GB18030增加的11个字符。
  2. GB2312 1区682个符号的编码。
  3. GBK 1区717个符号的编码。

结束语

通过本文的介绍,读者可以回答开头的问题了吗?

无论是Windows XP还是Vista,中文(中国)区域对应的默认代码页还是GBK。我们只能设置区域,并不能设置区域对应的默认代码页。所以在Windows世界,只要微软不愿意,GB18030就只是一张普通的代码页。目前的简体中文文档使用的编码主要是Unicode和GBK,应该没有什么文档会用GB18030保存。本文只是出于程序员的好奇而对GB18030编码所作的一些研究,希望能对同样好奇的读者有所助益。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:24995次
    • 积分:311
    • 等级:
    • 排名:千里之外
    • 原创:4篇
    • 转载:43篇
    • 译文:0篇
    • 评论:1条
    最新评论