中文字符编码 GB2312 GBK GB18030 BIG5 UNICODE UTF8 UTF16 UTF-32 USC-2 UCS-4

信息交换用汉字编码字符集·基本集 GB2312

概述

GB 2312 或 GB 2312-80 是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又称GB0

GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。

GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。

对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现。

分区表示

GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。

  • 01-09区为特殊符号。
  • 16-55区为一级汉字,按拼音排序。
  • 56-87区为二级汉字,按部首/笔画排序。

10-15区及88-94区则未有编码。

举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。

字节结构

在使用GB2312的程序通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。

每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。

“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上0xA0)。
由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是72*94=6768。
其中有5个空位是D7FA-D7FE。

例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节)0xA1(第二个字节)储存。}
(与区位码 1601 对比:0xB0=0xA0+16,0xA1=0xA0+1)。

汉字内码扩展规范 GBK

GBK即汉字内码扩展规范,K为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母。英文全称Chinese Internal Code Specification。

GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展(之前CP936和GB 2312-80一模一样)

后续国家标准GB18030技术上兼容GBK

编码方式

字符有一字节和双字节编码,007F范围内是一位,和ASCII保持一致,此范围内严格上说有96个文字和32个控制符号。

之后的双字节中,前一字节是双字节的第一位。总体上说第一字节的范围是81FE(也就是不含80FF),第二字节的一部分领域在407E,其他领域在80FE

具体来说,定义的是下列字节:

GBK的编码范围
范围第1字节第2字节编码数字数
水准 GBK/1 A1A9 A1FE 846 717
水准 GBK/2 B0F7 A1FE 6,768 6,763
水准 GBK/3 81A0 40FE (7F除外) 6,080 6,080
水准 GBK/4 AAFE 40A0 (7F除外) 8,160 8,160
水准 GBK/5 A8A9 40A0 (7F除外) 192 166
用户定义 AAAF A1FE 564  
用户定义 F8FE A1FE 658  
用户定义 A1A7 40A0 (7F除外) 672  
合计:  23,94021,886

双字节符号可以表达的64K空间如下图所示。绿色和黄色区域是GBK的编码,红色是用户定义区域。没有颜色区域是不正确的代码组合。

与其他编码的关系

GBK向下完全兼容GB2312-80编码。 支持GB2312-80编码不支持的部分中文姓,中文繁体,日文假名,还包括希腊字母以及俄语字母等字母。
不过这种编码不支持韩国字,也是其在实际使用中与unicode编码相比欠缺的部分。

上述GBK/1和GBK/2的领域即GB 2312-80用通常方法编码的区域。GB 2312 (正确说法是其根据EUC-CN的编码)和ISO/IEC 2022中调用GR其他的94字符集一样,
A1FE的范围开始读取字节对。这是上图中右下角的部分。
但是,GB 2312中对于
AAAFF8FE区域是空的,没有赋予编码。于是GBK就在这些领域里进行拓展。二者剩余部分作为用户定义区。

更重要的是,GBK进行了字节范围的扩展。ISO/IEC 2022中GR区域的字数有94=8,836字的限制。
只要放弃ISO/IEC 2022中针对图形文字和控制文字赋予严格的范围的模式,下位字节为单字节文字,上位字节对保留对应字符的功能,潜在的128²=16,384的代码位置就可以使用。
GBK采用其中的一部分,从A1FE (每个字节有94个选项) 的范围开始、第一字节向 81FE (126个选项) 、第二字节向 40FE (191个选项) 进行扩展。

与CP936字码表比较

微软的CP936通常被视为等同GBK,连 IANA 也以“CP936”为“GBK”之别名[1]。事实上比较起来, GBK 定义之字符较 CP936 多出95字(15个非汉字及80个汉字),
皆为其时未收入 ISO 10646 / Unicode 之符号:非汉字包括异体字符号[2]、十二个表意文字描述字符(Ideographic Description Characters)[3]及 GB 5007.1-85《信息交换用汉字 24x24 点阵字模集》附录对 GB 2312 增加,但 Unicode 未收之拼音符号“ḿ”和“ǹ”[4][5];汉字包括未收入 ISO 10646 的《简化字总表》汉字52个、《康熙字典》及《辞海》汉字部件28个[4]。CP936中的这95字分配到了Unicode的私有区域[6][7],现已全部收于新版 Unicode。

后续

中华人民共和国国家质量技术监督局于2000年3月17日推出了GB 18030-2000标准,以取代GBK。GB 18030-2000除保留全部GBK编码汉字,在第二字节把能使用范围再度进行扩展,增加了大约一百个汉字及四位元组编码空间,但是将GBK作为子集全部保留。请参看GB 18030。

信息技术 中文编码字符集GB18030 

GB 18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集,
是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK基本兼容,
支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。

GB 18030主要有以下特点:

  • 与 UTF-8 相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。
  • 编码空间庞大,最多可定义161万个字符。
  • 支持中国国内少数民族的文字,不需要动用造字区。
  • 汉字收录范围包含繁体汉字以及日韩汉字

本规格的初版是由中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000年3月17日发布。
现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005年11月8日发布,2006年5月1日实施。
此规格为在中国境内所有软件产品支持的强制规格。

字节结构

  • 单字节,其值从0到0x7F。(对应于ASCII码的相应码位)
  • 双字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x40到0xFE(不包括0x7F)。
  • 四字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节从0x81到0xFE,第四个字节从0x30到0x39。

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

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

版本

  • GB 18030-2000,兼容 Unicode 3.0 中日韩统一表意文字,共收27533个汉字;2000年3月17日发布
  • GB 18030-2005,更新至 Unicode 4.1 中日韩统一表意文字及增加少数民族文字,共有70244个汉字;2005年11月8日发布、2006年5月1日实施

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码位。调整方法应该与的调整方法相同。

B18030编码可以分为:单字节部分、双字节部分和四字节部分。单字节部分与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码位。

 

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的映射没有规律,只能通过查表方法映射。

大五码 BIG5

Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录13,060个汉字

在1990年代初期,当中国大陆的电子邮件和转码软件还未普遍之时,在深圳的港商和台商公司亦曾经使用Big5系统,以方便与总部的文件交流、以及避免为大陆的办公室再写一套不同内码的系统。
使用简体中文的社区,最常用的是GB 2312、GBK及其后续的国标码(GB 18030)。

2003年,Big5被收录到CNS11643中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为Big5-2003。

现在,除了台湾外,其他使用繁体汉字的地区,如香港、澳门,及使用繁体汉字的海外华人,都曾普遍使用Big5码做为中文内码及交换码。

Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为“高位字节”,第二个字节称为“低位字节”。

“高位字节”使用了0x81-0xFE,“低位字节”使用了0x40-0x7E,及0xA1-0xFE。在Big5的分区中: 

0x8140-0xA0FE 保留给用户自定义字符(造字区)
0xA140-0xA3BF 标点符号、希腊字母及特殊符号,
包括在0xA259-0xA261,安放了九个计量用汉字:兙兛兞兝兡兣嗧瓩糎。
0xA3C0-0xA3FE 保留。此区没有开放作造字区用。
0xA440-0xC67E 常用汉字,先按笔划再按部首排序。
0xC6A1-0xC8FE 保留给用户自定义字符(造字区)
0xC940-0xF9D5 次常用汉字,亦是先按笔划再按部首排序。
0xF9D6-0xFEFE 保留给用户自定义字符(造字区)

 

Code Page 950

Windows使用的Code Page 950 (系引用IBM Big 5码的编码页号Code Page 950,简称CP950)之中,只添加了上述0xF9D6-0xF9FE的倚天扩充字及表格符号,并没有加入日文假名字母等其他延伸。

在Windows ME之中,微软首度在0xA3E1加入了欧元(€)符号,之后所有Windows版本的Code Page 950也都有这个符号。

Big5-2003

鉴于Big5不是官方标准,中推会接受经济部标准检验局委托,召集台湾国内业者、专家和学者编制一个Big5的对照表,并把它放到台湾官方的CNS 11643附录里,正式成为官方标准的一部分。

在Big5-2003之中,收录了所有在1984年Big5编码的所有字符,另外再加入微软CP950的欧元符号、倚天延伸字集的0xA3C0-0xA3E0、0xC6A1-0xC7F2、0xF9D6-0xF9FE的用字。Big5-2003没有收录行列输入法特殊符号及0xC7F3-0xC875的俄语西里尔字母,理由是以CNS 11643没有这些字符。除此之外,所有倚天延伸全部收录。

相对于Big5-2003,最早没有加上延伸之Big5则对称为Big5-1984。

Unicode

Unicode(统一码、万国码、单一码、标准万国码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简化地方式来呈现和处理文字。

Unicode依随着通用字符集的标准而发展,同时也以书本的形式[1]对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为第六版,已收入了超过十万个字符(第十万个字符在2005年获采纳)。Unicode涵盖的数据除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。

Unicode发展是由非营利机构统一码联盟所负责,其致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往仅有有限的空间,亦不适用于多语环境。

Unicode备受认可,并广泛地应用于电脑软件的国际化与本地化过程。有很多新科技,如可扩展置标语言、Java编程语言,以及现代的操作系统,都采用Unicode编码。

Unicode的编码和实现

大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。

编码方式

统一码的编码方式与ISO 10646的通用字符集概念相对应。目前实际应用的统一码版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。
这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

上述16位统一码字符构成基本多文种平面。最新(但未实际广泛使用)的统一码版本定义了16个辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。
但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,即涵盖UCS-4的所有字符。
UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。

基本多文种平面的字符的编码为U+hhhh,其中每个h代表一个十六进制数字,与UCS-2编码完全相同。
而其对应的4字节UCS-4编码后两个字节一致,前两个字节则所有位均为0。

关于统一码和ISO 10646及UCS的详细关系,请参看通用字符集。

实现方式 - Unicode转换格式

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,
对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

例如,如果一个仅包含基本7位ASCII字符的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的8位始终为0。
这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。
而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。
这样对以7位ASCII字符为主的西文文档就大大节省了编码长度(具体方案参见UTF-8)。
类似的,对未来会出现的需要4个字节的辅助平面字符和其他UCS-4扩充字符,2字节编码的UTF-16也需要通过一定的算法进行转换。

再如,如果直接使用与Unicode编码一致(仅限于BMP字符)的UTF-16编码,由于每个字符占用了两个字节,在麦金塔电脑 (Mac)机和个人电脑上,对字节顺序的理解是不一致的。
这时同一字节流可能会被解释为不同内容,如某字符为十六进制编码4E59,按两个字节拆分为4E和59,在Mac上读取时是从低字节开始,
那么在Mac OS会认为此4E59编码为594E,找到的字符为“奎”,而在Windows上从高字节开始读取,则编码为U+4E59的字符为“乙”。
就是说在Windows下以UTF-16编码保存一个字符“乙”,在Mac OS环境下打开会显示成“奎”。此类情况说明UTF-16的编码顺序若不加以人为定义就可能发生混淆,
于是在UTF-16编码实现方式中使用了大端序(Big-Endian, 简写为UTF-16 BE)、小端序(Little-Endian,简写为UTF-16 LE)的概念,以及可附加的字节顺序记号解决方案,
目前在PC机上的Windows系统和Linux系统对于UTF-16编码默认使用UTF-16 LE。(具体方案参见UTF-16)

此外Unicode的实现方式还包括UTF-7、Punycode、CESU-8、SCSU、UTF-32、GB18030等,这些实现方式有些仅在一定的国家和地区使用,有些则属于未来的规划方式。
目前通用的实现方式是UTF-16小端序(LE)、UTF-16大端序(BE)和UTF-8。
在微软公司Windows XP附带的记事本(Notepad)中,“另存为”对话框可以选择的四种编码方式除去非Unicode编码的ANSI(对于英文系统即ASCII编码,中文系统则为GB2312或Big5编码)外,其余三种为“Unicode”(对应UTF-16 LE)、“Unicode big endian”(对应UTF-16 BE)和“UTF-8”。

目前辅助平面的工作主要集中在第二和第三平面的中日韩统一表意文字中,因此包括GBK、GB18030、Big5等简体中文、繁体中文、日文、韩文以及越南喃字的各种编码与Unicode的协调性被重点关注。考虑到Unicode最终要涵盖所有的字符。从某种意义而言,这些编码方式也可视作Unicode的出现于其之前的既成事实的实现方式,如同ASCII及其扩展Latin-1一样,后两者的字符在16位Unicode编码空间中的编码第一字节各位全为0,第二字节编码与原编码完全一致。但上述东亚语言编码与Unicode编码的对应关系要复杂得多。

非Unicode环境

在非Unicode环境下,由于不同国家和地区采用的字符集不一致,很可能出现无法正常显示所有字符的情况。
微软公司使用了代码页(Codepage)转换表的技术来过渡性的部分解决这一问题,即通过指定的转换表将非Unicode的字符编码转换为同一字符对应的系统内部使用的Unicode编码。
可以在“语言与区域设置”中选择一个代码页作为非Unicode编码所采用的默认编码方式,如936为简体中文GBK,950为正体中文Big5(皆指PC上使用的)。
在这种情况下,一些非英语的欧洲语言编写的软件和文档很可能出现乱码。而将代码页设置为相应语言中文处理又会出现问题,这一情况无法避免。
从根本上说,完全采用统一编码才是解决之道,但目前尚无法做到这一点。

代码页技术现在广泛为各种平台所采用。UTF-7的代码页是65000,UTF-8的代码页是65001。

UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。
它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。
因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。

UTF-8使用一至四个字节为每个字符编码:

  1. 128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  2. 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
  3. 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
  4. 其他极少使用的Unicode 辅助平面的字符使用四字节编码。

对上述提及的第四种字符而言,UTF-8使用四个字节来编码似乎太耗费资源了。
但UTF-8对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,
所以要决定UTF-8或UTF-16哪种编码比较有效率,还要视所使用的字符的分布范围而定。
不过,如果使用一些传统的压缩系统,比如DEFLATE,则这些不同编码系统间的的差异就变得微不足道了。
若顾及传统压缩算法在压缩较短文字上的效果不大,可以考虑使用Unicode标准压缩格式(SCSU)。

 

代码范围
十六进制
标量值(scalar value)
二进制
UTF-8
二进制/十六进制
注释

000000 - 00007F

128个代码

00000000 00000000 0zzzzzzz

0zzzzzzz(00-7F)

ASCII字符范围,字节由零开始

七个z

七个z

000080 - 0007FF

1920个代码

00000000 00000yyy yyzzzzzz

110yyyyy(C0-DF) 10zzzzzz(80-BF)

第一个字节由110开始,接着的字节由10开始

三个y;二个y;六个z

五个y;六个z

000800 - 00D7FF

00E000 - 00FFFF

61440个代码 [Note 1]

00000000 xxxxyyyy yyzzzzzz

1110xxxx(E0-EF) 10yyyyyy 10zzzzzz

第一个字节由1110开始,接着的字节由10开始

四个x;四个y;二个y;六个z

四个x;六个y;六个z

010000 - 10FFFF

1048576个代码

000wwwxx xxxxyyyy yyzzzzzz

11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz

将由11110开始,接着的字节由10开始

三个w;二个x;四个x;四个y;二个y;六个z

三个w;六个x;六个y;六个z

UTF-8编码字节含义

  • 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
  • 如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
  • 如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
  • 如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
  • 如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示;

因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;
根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),
可判断编码是否有错误或数据传输过程中是否有错误。

UTF-8的设计有以下的多字符组串行的特质:

  • 单字节字符的最高有效比特永远为0。
  • 多字节串行中的首个字符组的几个最高有效比特决定了串行的长度。最高有效位为110的是2字节串行,而1110的是三字节串行,如此类推。
  • 多字节串行中其余的字节中的首两个最高有效比特为10

UTF-8的这些特质,保证了一个字符的字节串行不会包含在另一个字符的字节串行中。这确保了以字节为基础的部份字符串比对(sub-string match)方法可以适用于在文字中搜索字或词。
有些比较旧的可变长度8位编码(如Shift JIS)没有这个特质,故字符串比对的算法变得相当复杂。虽然这增加了UTF-8编码的字符串的信息冗余,但是利多于弊。
另外,数据压缩并非Unicode的目的,所以不可混为一谈。即使在传送过程中有部份字节因错误或干扰而完全丢失,还是有可能在下一个字符的起点重新同步,令受损范围受到限制。

另一方面,由于其字节串行设计,如果一个疑似为字符串的串行被验证为UTF-8编码,那么我们可以有把握地说它是UTF-8字符串。
一段两字节随机串行碰巧为合法的UTF-8而非ASCII的机率为32分1。对于三字节串行的机率为256分1,对更长的串行的机率就更低了。

UTF-8的编码方式

UTF-8是UNICODE的一种变长度的编码表达方式《一般UNICODE为双字节(指UCS2)》,它由Ken Thompson于1992年创建,现在已经标准化为RFC 3629。
UTF-8就是以8位为单元对UCS进行编码,而UTF-8不使用大尾序和小尾序的形式,每个使用UTF-8存储的字符,除了第一个字节外,其余字节的头两个比特都是以"10"开始,
使文字处理器能够较快地找出每个字符的开始位置。

但为了与以前的ASCII码兼容(ASCII为一个字节),因此UTF-8选择了使用可变长度字节来存储Unicode:

Unicode和UTF-8之间的转换关系表
UCS-4编码UTF-8字节流
U+00000000 – U+0000007F 0xxxxxxx
U+00000080 – U+000007FF 110xxxxx 10xxxxxx
U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,
    这様的好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
  • 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。
    注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。

ASCII字母继续使用1字节存储,重音文字、希腊字母或西里尔字母等使用2字节来存储,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。

在UTF-8文件的开首,很多时都放置一个U+FEFF字符(UTF-8以EF,BB,BF代表),以显示这个文本文件是以UTF-8编码。

UTF-16

UTF-16是Unicode字符集的一种转换方式,即把Unicode的码位转换为16比特长的码元串行,以用于数据存储或传递。UTF是"Unicode/UCS Transformation Format"的首字母缩写,
即把Unicode字符转换为某种格式之意。UTF-16正式定义于ISO/IEC 10646-1的附录C,而RFC2781也定义了相似的做法。

UTF-16描述

Unicode的码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符. Unicode的码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。
每个平面的码位可表示为从U+xx0000到U+xxFFFF, 其中xx表示十六进制值从0016 到1016,共计17个平面。第一个平面成为基本多文种平面(Basic Multilingual Plane, BMP),
或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区段是永久保留不映射到字符,
因此UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

从U+0000至U+D7FF以及从U+E000至U+FFFF的码位

第一个Unicode平面(码位从U+0000至U+FFFF)包含了最常用的字符。该平面被称为基本多语言平面,缩写为BMP.
UTF-16与UCS-2编码这个范围内的码位为单个16比特长的码元,数值等价于对应的码位. BMP中的这些码位是仅有的码位可以在UCS-2被表示.

从U+10000到U+10FFFF的码位

辅助平面(Supplementary Planes)中的码位,在UTF-16中被编码为一对16比特长的码元(即32bit,4Bytes),称作 code units called a 代理对(surrogate pair), 具体方法是:

  • 码位减去0x10000, 得到的值的范围为20比特长的0..0xFFFFF.
  • 高位的10比特的值(值的范围为0..0x3FF)被加上0xD800得到第一个码元或称作高位代理(high surrogate), 值的范围是0xD800..0xDBFF.
    由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates).
  • 低位的10比特的值(值的范围也是0..0x3FF)被加上0xDC00得到第二个码元或称作低位代理(low surrogate), 现在值的范围是0xDC00..0xDFFF.
    由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates).

由于高位代理、低位代理、BMP中的有效字符的码位,三者互不重叠,搜索是简单的:
一个字符编码的一部分不可能与另一个字符编码的不同部分相重叠。这意味着UTF-16是自同步(self-synchronizing):
可以通过仅检查一个码元就可以判定给定字符的下一个字符的起始码元. UTF-8也有类似优点,但许多早期的编码模式就不是这样,必须从头开始分析文本才能确定不同字符的码元的边界.

由于最常有的字符都在基本多文种平面中,许多软件的处理代理对的部分往往得不到充分的测试。这导致了一些长期的bug与潜在安全漏洞,甚至在广为流行得到良好评价的应用软件

UTF-16解码
lead \ trail DC00DC01   …   DFFF
D800 10000 10001 103FF
D801 10400 10401 107FF
  ⋮
DBFF 10FC00 10FC01 10FFFF

 

从U+D800到U+DFFF的码位

Unicode标准规定U+D800..U+DFFF的值不对应于任何字符.

但是在使用UCS-2的时代,U+D800..U+DFFF内的值被占用,用于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不符合Unicode标准的字符映射正确的辨识、
转换成合规的码元 按照Unicode标准,这种码元串行本来应算作编码错误.

示例: UTF-16 编码程序

假设要将 U+64321 (16进位) 转成 UTF-16 编码. 因为它超过 U+FFFF, 所以他必须编译成32位(4个byte)的格式,如下所示:

V  = 0x64321
Vx = V - 0x10000
   = 0x54321
   = 0101 0100 0011 0010 0001

Vh = 01 0101 0000 // Vx 的高位部份的 10 bits
Vl = 11 0010 0001 // Vx 的低位部份的 10 bits
w1 = 0xD800 //結果的前16位元初始值
w2 = 0xDC00 //結果的後16位元初始值

w1 = w1 | Vh
   = 1101 1000 0000 0000
   |        01 0101 0000
   = 1101 1001 0101 0000
   = 0xD950

w2 = w2 | Vl
   = 1101 1100 0000 0000
   |        11 0010 0001
   = 1101 1111 0010 0001
   = 0xDF21

所以这个字 U+64321 最后正确的 UTF-16BE 编码应该是: 0xD950 0xDF21 而在小尾序中最后的编码应该是:0x50D9 0x21DF, 因为这个字超过 U+FFFF 所以无法用 UCS-2 的格式编码

16进制编码范围UTF-16表示方法(二进制)10进制码范围字节数量
U+0000---U+FFFF xxxxxxxx xxxxxxxx 0-65535 2
U+10000---U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111 4

UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2字节)存储,但UTF-16却无法兼容于ASCII编码。

UTF-16的编码模式

UTF-16的大尾序和小尾序存储形式都在用。一般来说,以Macintosh制作或存储的文字使用大尾序格式,以Microsoft或Linux制作或存储的文字使用小尾序格式。

为了弄清楚UTF-16文件的大小尾序,在UTF-16文件的开首,都会放置一个U+FEFF字符作为Byte Order Mark(UTF-16LE以FF FE代表,UTF-16BE以FE FF代表),
以显示这个文本文件是以UTF-16编码,其中U+FEFF字符在UNICODE中代表的意义是ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。

UTF-16与UCS-2的关系

UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。
现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。

“ABC”这三个字符用各种方式编码后的结果如下:

 

UTF-16BE 00 41 00 42 00 43
UTF-16LE 41 00 42 00 43 00
UTF-16(Big Endian) FE FF 00 41 00 42 00 43
UTF-16(Little Endian) FF FE 41 00 42 00 43 00
UTF-16(不带BOM) 00 41 00 42 00 43

Microsoft Windows操作系统内核对Unicode的支持

Windows (2000>>) 操作系统内核中的字符表示为UTF-16小尾序,可以正确处理、显示以4字节存储的字符。
但是Windows API实际上仅能正确处理UCS-2字符,即仅以2字节存储的,码位小于U+FFFF的Unicode字符。
其根源是Microsoft C++语言把wchar_t数据类型定义为16比特的unsigned short,这就与一个wchar_t型变量对应一个宽字符,可以存储一个Unicode字符的规定相矛盾。
相反,Linux平台的GCC编译器规定一个wchar_t是4字节长度,可以存储一个UTF-32字符,宁可浪费了很大的存储空间。

// 此源文件在Windows平台上必须保存为Unicode格式(即UTF-16小尾)
// 因为包含的汉字“𪚥”,不能在Windows默认的代码页936(即gbk)中表示。
// 该汉字在UTF-16小尾序中用4个字节表示,
// Windows操作系统能正确显示这样的在UTF-16需用4字节表示的字符
// 但是Windows API不能正确处理这样的在UTF-16需用4字节表示的字符,把它判定为2个UCS-2字符
 
#include <windows.h>
int main()
{
        const wchar_t lwc[]=L"𪚥";
 
        MessageBoxW(NULL, lwc, lwc, MB_OK);
 
        int i = wcslen(lwc);
        printf("%d\n", i);
        int j = lstrlenW(lwc);
        printf("%d\n", j);
 
        return 0;
}

UTF-32

UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。
与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符。 仅用’ABC’举例:

UTF-32BE 00 00 00 41 00 00 00 42 00 00 00 43
UTF-32LE 41 00 00 00 42 00 00 00 43 00 00 00
UTF-32(Big Endian) 00 00 FE FF 00 00 00 41 00 00 00 42 00 00 00 43
UTF-32(Little Endian) FF FE 00 00 41 00 00 00 42 00 00 00 43 00 00 00
UTF-32(不带BOM) 00 00 00 41 00 00 00 42 00 00 00 43

 

UCS-2 UCS-4

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之外。

UCS-2是一种编码格式,同时也是指以一一对应关系的Unicode实现。在UCS-2中只能表示U+0000到U+FFFF的BMP(Basic Multilingual Plane ) Unicode编码范围,
属于定长的Unicode实现,而UTF-16是变长的,类似于UTF-8的实现,但是由于其字节长度的增加,所以BMP部分也做到了一一对应,
但是其通过两个双字节的组合可以做到表示全部Unicode,表示范围从U+0000 到 U+10FFFF。

区位码 内码 代码页

区位码 : GB2312 标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为“位”。所以也称为区位码

内码 : 指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。
微软一般将缺省代码页指定的编码说成是内码。内码这个词汇,并没有什么官方的定义,代码页也只是微软这个公司的叫法。

啊 的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。
为了兼容00-7f的ASCII编码,我们在区位码的高、低字节上分别加上A0。这样“啊”的内码就成为0xB0A1

代码页 : 所谓代码页 (codepage) 就是各国的文字编码和 Unicode 之间的映射表。例如 GBK 和 Unicode 的映射表就是 CP936,所以也常用 cp936 来指代 GBK。
Windows中有缺省代码页的概念,即缺省用什么编码来解释字符, 缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。

 

1. big endian 和 little endian

在 UTF-16 或者 UCS 的编码中经常遇到这两个选项,big endian 和little endian 是 CPU 处理多字节数的不同方式。例如“汉”字的 Unicode/UCS 编码是 6C49。
那么写到文件里时,究竟是将 6C 写在前面,还是将 49 写在前面?如果将 6C 写在前面,就是 big endian。还是将 49 写在前面,就是 little endian。

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

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

2. UTF 的字节序和 BOM

BOM 称为 "Byte Order Mark"。UTF-8 以字节为编码单元,没有字节序的问题。而 UTF-16 以两个字节为编码单元,在解释一个 UTF-16 文本前,
首先要弄清楚每个编码单元的字节序。例如收到一个 "奎" 的 Unicode/UCS 编码是 594E,"乙" 的 Unicode/UCS 编码是 4E59。如果我们收到 UTF-16 字节流 "594E",
那么这是 "奎" 还是 "乙"?

在 Unicode/UCS 编码中有一个叫做 "ZERO WIDTH NO-BREAK SPACE" 的字符,它的编码是 FEFF。而 FFFE 在 Unicode/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 来标记文本文件的编码方式的。

3. Unicode 和 UCS 的区别

Unicode 标准额外定义了许多与字符有关的语义符号学,一般而言是对于实现高质量的印刷出版系统的更好的参考。Unicode 详细说明了绘制某些语言 (比如阿拉伯语) 表达形式的算法,
处理双向文字 (比如拉丁与希伯来文混合文字) 的算法和排序与字符串比较所需的算法,以及其他许多东西。

另一方面,UCS(ISO-10646) 标准,只不过是一个简单的字符集表。它指定了一些与标准有关的术语。定义了一些编码的别名。并包括了规范说明。
指定了怎样使用 UCS 连接其他 ISO 标准的实现,比如 ISO-6429 和 ISO-2022。还有一些与 ISO 紧密相关的,比如 ISO-14651 是关于 UCS 字符串排序的。

考虑到 Unicode 标准有一个易记的名字,且在任何好的书店里的 Addison-Wesley 里有,只花费 ISO 版本的一小部分,且包括更多的辅助信息,
因而它成为使用广泛得多的参考也就不足为奇了。然而,一般认为,用于打印 ISO-10646-1 标准的字体在某些方面的质量要高于用于打印 Unicode 2.0 的。
专业字体设计者总是被建议说要两个标准都实现,但一些提供的样例字形有显著的区别。ISO-10646-1 标准同样使用四种不同的风格变体来显示表意文字如中文,
日文和韩文 (CJK),而 Unicode 2.0 的表里只有中文的变体。这导致了普遍的认为 Unicode 对日本用户来说是不可接收的传说,尽管是错误的。

 

posted @ 2013-05-07 20:38  IAmAProgrammer  阅读(3583)  评论(0编辑  收藏  举报