英文字母再加一些其他标点字符之类的也不会超过256个,用一个字节来表示一个字符就足够了(2^8=256)。但其他一些文字不止这么多字符,比如中文中的汉字就多达10多万个,一个字节只能表示256个字符,肯定是不够的,因此只能使用多个字节来表示一个字符。
因此,GB系列编码方案向下完全直接兼容ASCII编码方案。也就是说,如果一段用GB编码方案编码的文本里的所有字符都在ASCII编码方案中有定义(即该文本全部由ASCII字符组成),那么这段GB编码实际上和ASCII编码完全一样。
这里要指出的是,虽然都用多个字节表示一个字符,但是GB类的汉字编码与后文的Unicode编码方案的UTF-8、UTF-16、UTF-32等字符编码方式CEF是毫无关系的(其中UTF-8对于ASCII字符仍用一个字节编码,而非ASCII字符则为多字节编码)。
比如,当多字节字符与原先的ASCII字符混用时:
GB2312编码为了避免与ASCII字符编码(0~127)相冲突,规定表示一个汉字的编码(即汉字内码)的字节其值必须大于127(即字节的最高位为1),并且必须是两个大于127的字节连在一起来共同表示一个汉字(GB2312为双字节编码),前一字节称为高字节,后一字节称为低字节;而一个字节的值若小于等于127(即字节的最高位为0),自然是仍表示一个原来的ASCII字符(ASCII为单字节编码)。
因此,可以认为GB2312是对ASCII的中文扩展(即GB2312完全直接兼容ASCII),正如EASCII是对ASCII的欧洲文字扩展一样,都可以称为ASCII超集、8位版等。
不过,很显然的是,GB2312与EASCII码的128~255这段扩展部分所表示的字符是不同的。也就是说,GB2312与EASCII虽然都兼容ASCII,但GB2312并不兼容EASCII的扩展部分。(ps:GB2312与EASCII对ASCII码的扩展互不兼容)
事实上,目前世界上除ASCII之外的其它通行的字符编码方案,基本上都兼容ASCII(包括直接兼容与间接兼容,详见后文介绍),但相互之间除了兼容ASCII字符的部分之外却并不兼容。
GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,除了汉字,GB2312还收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。
可能是出于显示上视觉美观的考虑,除汉字之外的682个字符中,GB2312甚至还包括了ASCII里本来就有的数字、标点、字母等字符。也就是说,这些ASCII里原来就有的单字节编码的字符,又再编了两个字节长的GB2312编码版本。
这682个双字节编码字符就是常说的“全角”字符,而这些字符所对应的单字节编码的ASCII字符就被称之为“半角”字符。
早期的点阵显示器上由于像素有限,原先ASCII西文字符的显示宽度(比如8像素的宽度)用来显示汉字有些捉襟见肘(实际上早期的针式打印机在打印输出时也存在这个问题),因此就采用了两倍于ASCII字符的显示宽度(比如16像素的宽度)来显示汉字。
这样一来,ASCII西文字符在显示时其宽度为汉字的一半。或许是为了在西文字符与汉字混合排版时,让西文字符能与汉字对齐等视觉美观上的考虑,于是就设计了让西文字母、数字和标点等特殊字符在外观视觉上也占用一个汉字的视觉空间(主要是宽度),并且在内部存储上也同汉字一样使用2个字节进行存储的方案。这些与汉字在显示宽度上一样的西文字符就被称之为全角字符。
后来,其中的一些全角字符因为比较有用,就得到了广泛应用(比如全角的逗号“,”、问号“?”、感叹号“!”、空格“span”等,这些字符在输入法中文输入状态下的半角与全角是一样的,英文输入状态下全角跟中文输入状态一样,但半角大约为全角的二分之一宽),专用于中日韩文本,成为了标准的中日韩标点字符。而其它的许多全角字符则逐渐失去了价值(现在很少需要让纯文本的中文和西文字符对齐了),就很少再用了。
现在全球字符编码的事实标准是Unicode字符集及基于此的UTF-8、UTF-16等编码实现方式。Unicode吸纳了许多遗留(legacy)编码,并且为了兼容性而保留了所有字符。因此中文编码方案中的这些全角字符也保留下来了,而国家标准也仍要求字体和软件都支持这些全角字符。
不过,半角和全角字符的关系在UTF-8、UTF-16等中不再是简单的1字节和2字节的关系了。具体参见后文。
GB2312-1980共收录6763个汉字,覆盖了中国大陆99.75%的使用频率,基本满足了汉字的计算机处理需要。
但对于人名、古汉语等方面出现的罕用字、生僻字,GB2312不能处理,如部分在GB2312-1980推出以后才简化的汉字(如“啰”)、部分人名用字(如歌手陶喆的“喆”字)、台湾及香港使用的繁体字、日语及朝鲜语汉字等,并未收录在内。
虽然GBK跟GB2312一样是双字节编码,但GBK只要求第一个字节即高字节大于127就固定表示这是一个汉字的开始(即GBK编码高字节的首位必须是1;0~127当然表示的还是ASCII字符),不再像GB2312一样要求第二个字节即低字节也必须大于127(即GBK编码低字节首位既可以是0,也可以是1)。
正因为如此,作为同样是双字节编码的GBK才可以收录比GB2312更多的字符。
GBK字符集向后完全兼容GB2312,同时还支持GB2312-1980不支持的部分中文简体、中文繁体、日文假名(不过这个编码不支持韩国文字,也是其在实际使用中与Unicode编码相比欠缺的部分),共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一体。
GBK的编码框架(CodeScheme):其中GBK/1收录除GB2312字符外的其他增补字符,GBK/2收录GB2312字符,GBK/3收录CJK字符,GBK/4收录CJK字符和增补字符,GBK/5为非中文字符,UDC为用户自定义字符。
微软的CP936通常被视为等同于GBK,连IANA(InternetAssignedNumbersAuthority互联网号码分配局)也将“CP936”视作“GBK”的别名。
但事实上比较起来,GBK定义的字符较CP936多出了95个(15个非汉字及80个汉字),都是当时没有收入ISO/IEC10646(即UCS)/Unicode的字符。(UCS、Unicode后文有详细介绍)。
GB18030《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
2005年,GB18030编码方案在GB18030-2000的基础上又进行了扩充,于是又有了GB18030-2005《信息技术中文编码字符集》。
该标准第一次颁布是在1993年,当时只颁布了其第一部分,即ISO/IEC10646.1:1993,除了收录了世界上其他文字字符之外,其中也收录了中国大陆、台湾、日本及韩国的汉字,总共20,902个。
为了与国际标准接轨,中国于是制定了与ISO/IEC10646.1:1993标准相对应的中国国家标准—GB13000.1-1993《信息技术通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》。
2010年又发布了其替代标准—GB13000-2010《信息技术通用多八位编码字符集(UCS)》,此标准等同于国际标准ISO/IEC10646:2003《信息技术通用多八位编码字符集(UCS)》。
CJK是中文(Chinese)、日文(Japanese)、韩文(Korean)三国文字英文首字母的缩写。顾名思义,它能够支持这三种文字,但实际上,CJK能够支持包括中文(包含壮文)、日文、韩文、越文在内的多种亚洲双字节文字。
所谓“起源相同、本义相同、形状一样或稍异的表意文字”,主要为汉字,包括繁体字、简体字;但也有仿汉字,包括方块壮字、日本汉字(漢字/かんじ)、韩国汉字(漢字/)、越南的喃字(喃/ChNm)与儒字(儒/ChNho)等。ps:越南汉字看不懂。。。
注意,这里的“GB类字符集”指的是除了单字节编码的ASCII字符之外的部分,因此属于狭义;严格来讲,广义上的“GB类字符集”包括了单字节编码的ASCII字符以及双字节编码的非ASCII字符,因此广义上的GB类字符集属于单字节与双字节混合字符集。在一段表述中,具体指的是狭义还是广义,需根据上下文而定。
基于DBCS的编码方案里,最大的特点是两字节长的中文字符和一字节长的英文字符(ASCII字符)完全兼容,可以并存于同一个文件内。
因此,在使用基于DBCS的编码方案的年代,写程序时为了支持中文处理,必须要注意字符串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。
使用GB类编码方案时一般都要时刻记住:一个汉字由两个字节组成(即一个汉字占用的存储空间相当于两个英文字符所占用的存储空间)。
下面以GB2312码为例来加以说明。
换言之,GB2312将包括汉字在内的所有字符编入一个94*94的二维表,行就是“区”、列就是“位”,每个字符由区、位唯一定位,其对应的区、位编号合并就是区位码。
比如“万”字在45区82位,所以“万”字的区位码是:4582(注意,GB类汉字编码为双字节编码,因此,45相当于高位字节,82相当于低位字节)。
ps:多字节码元中,第一个字节即高位字节,后面的字节即低位字节。
在GB2312字符集中:
01~09区(682个):特殊符号、数字、英文字符、制表符等,包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母等在内的682个全角字符;
10~15区:空区,留待扩展;
16~55区(3755个):常用汉字(也称一级汉字),按拼音排序;
56~87区(3008个):非常用汉字(也称二级汉字),按部首/笔画排序;
88~94区:空区,留待扩展。
因此,必须将“区码”和“位码”分别加上32(十六进制为20,可写作20H或0x20,后缀H或前缀0x都可表示十六进制),作为国标码。也就是说,国标码相当于将区位码向后偏移了32,以避免与ASCII字符中0~32的不可显示字符和空格字符相冲突。
**注意,**国标码中是分别将区位码中的“区”和“位”各自加上32(20H)的,因为GB2312是DBCS双字节字符集,因此国标码属于双字节码。
这样我们可以算出“万”字的国标码十进制为:(45+32,82+32)=(77,114),十六进制为:(4D,72),二进制为:(01001101,01110010)。
不过国标码还不能直接在计算机上使用,因为这样还是会和早已通用的ASCII码冲突,从而导致乱码。
由于ASCII码只用了一个字节中的低7位,所以,这个首位(最高位)上的“1”就可以作为识别汉字编码的标志,计算机在处理到首位是“1”的编码时就把它理解为汉字,在处理到首位是“0”的编码时就把它理解为ASCII字符。
比如:
77+128=205(二进制为11001101,十六进制为CD);
114+128=242(二进制为11110010,十六进制为F2)。
从区位码(国家标准定义)--->区码和位码分别+32(即+20H)得到国标码--->再分别+128(即+80H)得到机内码(与ACSII码不再冲突)。
因此,区位码的区和位分别+160(即+A0H,32+128=160)可直接得到内码。用十六进制表示就是:
区位码(区码,位码)+(20H,20H)+(80H,80H)=国际码(区码,位码)+(A0H,A0H)=内码(高字节,低字节)。
注:十六进制数既可通过添加后缀H来表示,也可通过添加前缀0x来表示。
区位码、国标码、内码的转换(ps:可以理解为三码相对ASCII各自做偏移)非常简单,但令人迷惑的是为什么要这么转换?
首先,需要注意到一点,GB2312虽说是汉字编码方案,但其实里面也有针对26个英文字母和一些特殊符号的编码,按理说这些和ASCII重合的字符(33~127)应该无需再重新编码,直接沿用ASCII编码不就行了?
原来,当时在制定GB2312时,决定对ASCII中的可打印字符,也就是英文字母、数字和符号部分(33~126,127为不可打印的DEL)重新编入GB2312中,以两个字节表示,称之为全角字符(全角字符在屏幕上的显示宽度为ASCII字符的两倍,后来也因此而将对应的ASCII字符称之为半角字符)。
而对于ASCII中前32个不可显示也不可打印的控制字符(ASCII码为0~31),以及第33个可显示但不可打印的空格字符(ASCII码为32)等一共33个不可打印字符的编码则直接沿用,不再重新编码。
因为要保留这33个不可打印字符,就不能直接采用区位码作为计算机直接处理的机内码,需要将区位码向后偏移32以避开冲突(为什么是偏移32,而不是偏移33?因为区位码中的区码和位码都是从1开始计数的,不像ASCII码是从0开始计数的)。
十进制数字32的十六进制表示就是20H,这也就是区位码的区码和位码都分别要加上20H才能得到国标码的原因。
不过,如果直接采用国标码作为计算机直接处理的机内码的话,还是会与SCII编码产生冲突,导致乱码。
因为国标码虽然相较于区位码避开了ASCII码中0~32的前33个不可打印字符,但并没有避开ASCII码中的英文字母、数字和符号等可打印字符(33~126,共94个字符)以及不可打印的DEL(127)。也就是说,国标码并不是完全兼容ASCII码的。
ps:前文说了,GB2312采用了全新的双字节(即全角)33~126、127等,原版基础ASCII是半角字符,这是不兼容的。
为了彻底避免与ASCII码的冲突,考虑到ASCII码只使用了一个字节中的低7位,其最高位(即首位)总是为0,于是决定将国标码中每个字节的最高位设为1(国标码的两个字节中的最高位都总是为0,即国标码中的每个字节与ASCII码一样实际上也只用了一个字节中的低7位),这就是GB2312的机内码(即内码),简称GB2312码。
这样一来就彻底区分开了ASCII码和GB2312码。这也是为什么国标码还要加上(80H,80H)才能得到机内码的原因。
原文:看到这里,有人或许又要问了:如果仅仅是为了避免与ASCII码相冲突,为什么最初不直接将区位码的区码和位码的最高位从0改为1(相当于各自直接加上128),这样不就无需经过国标码多此一举的中间转换了吗?而且还无需后移32,也就不用浪费这部分编码空间。
对此本人也很困惑,在网上搜了很久也没找到答案,因此具体原因不得而知。或许是一开始考虑不周?或许是为了未来扩展所需而预留一部分空间?又或许是有其他不得已的原因?有知道的朋友还望能指点迷津。
GB2312区位码、国标码、内码对照表(其中汉字内码B0A1~F7FE,共6763个)
英文字母只有26个,可以把所有的字符都放到键盘上(所以西欧、美国等编码标准没有输入码,即不需要外码),而使用这种办法把所有的汉字都放到键盘上是不可能的。所以汉字系统需要有自己的输入码体系,使汉字与键盘能建立起对应关系(映射关系)。
目前常用的汉字外码分为以下几类:
数字编码,比如区位码;
拼音编码,比如全拼、双拼、自然码等;
字形编码,比如五笔、表形码、郑码等。
汉字外码往往会出现重码。所谓重码,指的是同一个汉字外码对应于多个汉字,反过来说,也就是可能有多个汉字的外码是相同的,相当于重复了,所以称之为“重码”。比如使用拼音作为外码时(即使用拼音输入法输入汉字时,同音字相当多),重码现象是相当普遍的。
当出现重码时,往往需要附加选择编号以具体确定所要输入的汉字(输入法打出汉字后,按键盘数字键选择哪个汉字),这种情况下,可认为外码实际上相当于隐式地包括了选择编号在内。
为了将汉字在显示器或打印机上输出,把汉字按图形符号设计成点阵图,就得到了相应的点阵代码(字形码)。
也就是用0、1表示汉字的字形,将汉字放入n行*n列的正方形(即点阵)内,该正方形共有n^2个小方格,每个小方格用一位二进制数表示,凡是笔划经过的方格其值为1,未经过的方格其值为0。
显示一个汉字一般采用16×16点阵或24×24点阵或48×48点阵。已知汉字点阵的大小,可以计算出存储一个汉字所需占用的字节空间。
比如,用16×16点阵表示一个汉字,就是将每个汉字用16行,每行16个点表示,一个点需要1位二进制数,16个点需用16位二进制数(即2个字节),所以需要16行×2字节/行=32字节,即以16×16点阵来表示一个汉字,字形码需要32字节。
因此,字节数=点阵行数×(点阵列数/8)。
ps:各位需要注意,原文中的汉字点阵,并非显示器的像素点阵,不能混淆了概念。
字形码主要是计算机内部用于输出(显示输出或打印输出)字形之用,我们所看到的只是文字字形而已,字形码本身是无法直接“看到”的。
显然,字形码所表示的字符,相对于抽象字符表ACR里的“抽象”字符,可称之为“具体”字符,因为已经具有了“具体”的外形。
为了将汉字的字形显示输出或打印输出,汉字信息处理系统还需要配有汉字字形库,也称字模库,简称字库,它集中存储了汉字的字形信息。
字库按输出方式可分为显示字库和打印字库。用于显示输出的字库叫显示字库,工作时需调入内存。用于打印输出的字库叫打印字库,工作时无需调入内存。
字库按存储方式也可分为软字库和硬字库。软字库以字体文件(即字形文件)的形式存放在硬盘上,现多用这种方式(软字库)。硬字库则将字库固化在一个单独的存储芯片中,再和其它必要的器件组成接口卡,插接在计算机上,通常称为汉卡。这种方式现已淘汰。
可以这样理解,为在计算机内表示汉字而采取统一的编码方式所形成的汉字编码叫内码。为方便汉字输入而形成的汉字编码为外码,也叫输入码。为显示输出和打印输出汉字而形成的汉字编码为字形码,也称为字模码、输出码。
通过键盘输入汉字外码,然后输入法将汉字外码(输入码)转换为当前操作系统所默认采用的字符编码方案的字符编号(即码点值),再根据字符编号通过代码页查表的方式转换为汉字内码(机内码)(代码页详见后文的介绍),以实现输入汉字的目的;然后根据所选择的字体,通过汉字内码在字模库(即字库)中找出与字体相对应的字形码(输出码),从而将汉字内码转换为汉字字形码,以实现显示输出和打印输出汉字的目的。
事实上,英文字符的输入、处理和显示过程大致上也差不多,只不过英文字符不需要输入码(即外码),直接在键盘上输入对应的英文字母即可。
如果一定要套用的话,就GB系列编码而言,勉强来说,区位码相当于现代字符编码模型中编号字符集CCS的字符编号,国标码相当于字符编码方式CEF的码元序列,而机内码则相当于字符编码模式CES的字节序列。
不过,由于GB系列编码虽然是多字节编码,但码元却是单字节码元(码元的概念后文有详细介绍),因此不存在字节序问题,也就不存在字符编码模式CES中的大端序、小端序的概念(字节序以及大端序、小端序的概念后文有详细介绍)。
所以,即使知道是ANSI编码,还需要知道这是哪一个国家或地区的才能解码;而且,同一份文本,只能采用一种ANSI编码方案来编码,比如,无法用同一种ANSI编码来表示既有汉字、又有韩文的文本。
严格来说,ANSI的字面意思并非字符编码,而是美国的一个非营利组织—美国国家标准学会(AmericanNationalStandardsInstitute)的缩写。ANSI这个组织做了很多标准制定工作,包括C语言规范ANSIC,还有与各国和地区既兼容ASCII又互相不兼容的字符编码相对应的“代码页(CodePage)”标准。
比如ANSI规定简体中文GB编码的代码页是936,所以GB编码又叫做ANSICodePage936(ANSI标准的代码页936)。
各国和地区既兼容ASCII又互相不兼容的字符编码之所以被微软统称为ANSI编码的原因即在于此。
后来,或许是出于沿用统一的称呼之目的,有些在当时还并未被ANSI定为标准的代码页,也被微软称之为ANSI代码页,比如CP943代码页。
在Windows系统的编码处理中,ANSI编码一般代表系统默认的编码方式,而且并不是确定的某一种编码方式—在简体中文操作系统中ANSI编码默认指的是GB系列编码(GB2312、GBK、GB18030);在繁体中文操作系统中ANSI编码默认指的是Big5编码(港澳台地区使用的繁体汉字编码);在日文操作系统中ANSI编码默认指的是ShiftJIS编码,等等。可在系统区域设置的系统Locale中查看、更改。(本文后面有详细介绍)
现代操作系统中不同的语言和区域设置可能使用不同的代码页。
后来代码页进一步扩展,除了原先针对ANSI编码所定义的ANSI代码页,针对Unicode字符集的各UTF编码方式(UTF-8、UTF-16、UTF-32等),各个组织和厂商往往也定义了相应的代码页。
需要注意的是,在实践中,代码页一般与其所直接对应的字符集之间并非完全等同,往往因为种种原因(比如标准跟不上现实实践的需要)而会对字符集有所扩展。
代码页可以体现为从字符映射到单字节值或多字节值的一张表格。
注意,针对ANSI编码而言,虽然其属于传统字符编码模型,但从现代字符编码模型的角度来看,这里所提到的单字节值与多字节值指的是特定于系统平台的物理意义上的字节序列,不是指与系统平台无关的逻辑意义上的码元序列(虽然对属于传统字符编码模型的早期字符编码方案而言,字符的码元序列与字节序列其实是一样的)。
而针对属于现代字符编码模型的Unicode字符集的各UTF编码方式而言,则更是同样如此。比如针对UTF-16所定义的代码页,其存储的是针对UTF-16这种字符编码方式CEF的某种字符编码模式CES(即大端序或小端序之一,大端序、小端序的概念后文有详细介绍)。
正因为这样,代码页也被称之内码表。
也就是说,代码页是字符集在计算机中的具体编码实现;特别是从现代字符编码模型的角度而言,代码页可认为是字符集的某种字符编码方式CEF的具体字符编码模式CES在计算机中的具体实现,可以将其理解为一张“字符-字节”(或更准确地理解为“字符-字节序列”)映射表,计算机通过查表实现“字符-字节”之间的双向“翻译”。
代码页主要用于具体实现各编码方案中的字符在计算机系统中的物理存储和显示。当计算机读取了一个二进制字节,那这个字节到底属于哪个字符,就需要到存储在计算机中的某个代码页中查找,这个查找的过程就被称为查表。
比如,当使用输入码(即外码)输入汉字时,输入法软件需要将输入码(出现重码时另加选择编号)根据代码页转换为机内码(即查表)进行存储,以及再根据机内码和相应的字体设定到对应的字体文件中查找字形码进行显示(ps:前文有详细介绍,《1.4简体汉字编码实现>小结>汉字从输入到输出过程》)。
在Windows中,代码页是系统默认设置的(即默认系统区域设置),也可在(Windows7的,ps:Win10同理)“控制面板-区域和语言-管理-非Unicode程序的语言-更改系统区域设置”中选择列表中的语言进行更改。
**注意,**系统区域设置SystemLocale可用于确定在不使用Unicode编码的程序(即非Unicode程序)中输入和显示字符的默认编码方案(显然主要是指ANSI编码方案)和字体,这样就可以让非Unicode程序在计算机上使用指定的语言(实质上是使用指定的ANSI编码)得以正常运行。
因此,在计算机上安装某些非Unicode程序时,如果出现乱码,则可能需要更改默认的系统区域设置。为系统区域设置选择不同的语言并不会影响Windows系统本身或其他使用Unicode编码方案的程序(即Unicode程序)的语言显示。
不过,现在Unicode编码方案已经成为了主流,非Unicode程序已经难得一见了。
微软为了适应世界上不同地区用户的文化背景和生活习惯,在Windows中设计了区域(Locale)设置的功能。
可以在Windows控制面板的“区域和语言选项”中设置系统Locale(非Unicode程序的语言)和用户Locale(标准和格式)。
系统Locale对应的代码页被作为Windows系统的默认代码页。在没有明确指定某个文本所采用的编码方案时,Windows系统将按照系统Locale中所指定的默认代码页(实质上代表了某个编码方案)来解释该文本数据。这个默认代码页通常被称作ANSI代码页(简称ACP;注意,如前所述,虽然非ANSI编码的Unicode各UTF编码也同样定义了代码页,但系统Locale中所设定的默认代码页之所以通常被称为ACP,是因为系统Locale主要是针对非Unicode程序而设置的)。
在WindowsXP的“区域和语言选项”高级页面的“代码页转换表”中,可看到各种语言(实质上是各个编码方案)的代码页(但Windows7中已经不能直接看到了)。