字符集与编码(九)——GB2312,GBK,GB18030

試著忘記壹切 提交于 2020-01-07 18:44:44

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

前面的一些篇章更多谈论了Unicode的相关话题,虽然也有提到GBK等编码,但都没细说,这里打算系统说一下。GB系列包括GB2312,GBK,GB18030.

前面已经提过,GB=Guo Biao=国标=国家标准,至于所谓的2312就是一编号了,没有其它特别的意义,18030类似。GBK没有编号,所以它实际上并不是国家标准,只是一个事实标准,GBK中K指“扩展”的意思。

最早的是GB2312,我们从它开始说起。

GB2312


以下为一简介(官方文档见"国家标准化管理委员会"网站:http://gbread.sac.gov.cn/bzzyReadWebApp/standardresources.action?m=readFile&bzNum=GB%202312-1980&flag=1,用IE打开,它要安装一个ActiveX插件):

GB 2312-1980,全称《信息交换用汉字编码字符集 基本集》,由国家标准总局于1980年3月9号发布,1981年5月1日实施,通行于大陆。新加坡等地也使用此编码。它是一个简化字的编码规范,也包括其他的符号、字母、日文假名等,共7445个图形字符,其中汉字占6763个。

上述官网地址无法下载,如果你想下载,可试下这个ftp://ftp.oreilly.com/examples/cjkvinfo/AppE/gb2312.pdf(比标准方案多增加了一些字符)

作为一个编码字符集而言,前面也曾说到,它采用了所谓的二维区位编号,下面是一个概览图:

image

它是一个94×94的表格,理论上有94×94=8836个空间。

横的叫区,竖的叫位,总共94个区,区和位的编号都从1开始。可以看到粗略有三大部分。

94个区

1. 中间黑色的主体部分即是汉字区了,具体为16-87区,共87-16+1=72个区,理论空间为72×94=6768.

从上图中可以看到中间有5个编码为空白(中间靠右边部分,55区最后5个位),所以总共有6768-5=6763个汉字。

一级汉字与二级汉字:

image

第16-55区:一级汉字,3755个(以拼音字母排序)
第56-87区:二级汉字,3008个(以部首笔画排序)

2. 最下面的88-94区是有待进一步标准化的空白区。

3. 关于前面的01-15区,下图为概览图左上角的局部放大图:

image

1. 01-09区为符号、字母、日文假名等,部分区还有空白位。

03区即是对应ASCII字符的全角字符区。输入法的全角模式下输入的即是这些字符。

2. 10-15区也是有待进一步标准化的空白区。

各区的一个具体情况:

第01区:中文标点、数学符号以及一些特殊字符
第02区:序号
第03区:全角西文字符
第04区:日文平假名
第05区:日文片假名
第06区:希腊字母表
第07区:俄文字母表
第08区:中文拼音字母表
第09区:制表符号 
第10-15区:未定义
第16-55区:一级汉字(以拼音字母排序)
第56-87区:二级汉字(以部首笔画排序)
第88-94区:未定义

区位码

在上图中还标出了一个汉字“啊”,它就是GB2312方案中的天字第一号汉字,它处于16区01位上,所以它的区位码即是1601.

所谓区位码就是这一94×94的大表格中的行号与列号了,均从1开始编号。

第一个字符0101为“全角空格”(图中显示为SP(space))。

国标码

将区位码的区和位分别加上32(=0x20)就得到了国标码。

“啊”的区位码是16-01,分别加32,得到16+32-01+32=48-33,即是国标码。当然,你通常应该写成16进制,48-33即是0x30-0x21,所以3021即是“啊”十六进制的国标码,使用两字节保存,30为高字节,21为低字节。如下:

image

GB2312方案规定,对上述表中任意一个图形字符都采用两个字节表示,每个字节均采用七位编码表示。

如上图所示,只用了7位,这即是说最高位就是0了。

但为何不直接采用区位码呢?为什么要加32呢?你也许还记得前面说到ASCII时,前面32个字符是控制码,中文系统自然也不能少了这些控制码,为了不与这些控制码冲突,加上32就能跳过它们了。

一字节有128个空间,128-32=96,实际上,ASCII中第127个也是控制码(DEL, 删除),再减去就还有95个有效位,再加上区位从1开始,又损失了一位,所以最终只有94个有效位了,这也是前面为何是一个94×94的表格。

国标码的定位实际应该是与ASCII一致的,是作为国家信息交换的标准码。从设计上看,它并没打算兼容ASCII,它已经把ASCII中的字符收录了过来,不过是作为所谓的全角字符来看待,但全角英文显示效果其实是很差的,下面是全角英文的一个示例:

hello,world

显得非常不紧凑,最终,一种能兼容ASCII的存储方案得到了广泛采纳,这就是所谓的机内码了。

机内码

将国标码高低字节分别加上0x80(=128)就得到了机内码(有时又叫交换码)。128的二进制形式为10000000,加128,简单地讲,就是把国标码最高位置成1.至于为什么要这样呢?我想你应该也清楚了,就是要兼容ASCII,ASCII最高位为0,国标码加128后,高低字节的最高位都成了1,这样就与ASCII区分开来。

将“啊”的国标码3021分别加上0x80,0x30+0x80=0xB0,0x21+0x80=0xA1,所以B0A1即是机内码。

如果从区位码算起,那么则是加上0x20+0x80=32+128=160=0xA0,也即区位码的区和位分别直接加上0xA0即可得到机内码,如下图所示:

image

如果你新建一个文本文件,录入“啊”字,以GB2312编码方式保存(使用GBK即可,它兼容GB2312),再用十六进制查看,你会发现使用的是机内码:

image

使用代码的测试也可验证这一点:

    @Test
    public void testAh() throws UnsupportedEncodingException {
        String ah = "啊";
        assertThat(DatatypeConverter.printHexBinary(ah.getBytes("GB2312"))).isEqualTo("B0A1");
    }

虽然我们常把GB2312称为国标码,但我们应该清楚,实际存储使用的是机内码,通常说到GB2312编码时指的就是这个机内码了。它能兼容ASCII,是一种变长的编码方案,对ASCII中的字符(也即所谓的“半角西文字符”)采用一字节编码,最高位为0;对区位表中的字符采用两字节编码,且每字节最高位均为1,以此区分。

自然,全角英文字符就是两字节编码了,跟汉字是一样的。

下面是一个混合了汉字,半角字母a和全角字母a的编码示例,共5个字节:

image

我们说GB2312是一个变长编码方案,是站在其兼容ASCII编码角度而言,就其方案标准本身定义的字符而言,它是一个双字节定长编码方案。

你可能会想,那国标码还有什么用?

我个人觉得,国标码既然称为中文信息交换的标准码,必然要成为“机内”码才有意义,只不过由于各种原因,最终未能如愿。早期的一些系统或者一些小型的嵌入式系统或许采纳了它做为“机内”码。当然以上为个人猜测,仅供参考。

另:我在前面的一些文章中谈到区位码时把它与机内码混为一谈,特此更正。

下面是三种码在256×256坐标中的位置的一个示意图:

image

GBK


GBK是对GB2312的一个扩展,兼容GB2312,因此也兼容ASCII,也是一个变长编码方案。下面是一个简介:

GBK总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,总计23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号883 个。

GBK是国家有关部门与一些信息行业企业等一起合作推出的方案,但并未作为国家标准发布,只是一个事实上的标准,一个过渡方案,为GB18030标准作的一个准备。

首字节(lead byte)

下面是Windows Code page: 936 (GBK),第一字节的概况(来自http://msdn.microsoft.com/en-US/goglobal/cc305153.aspx

image

Code page 936实质上是GBK到UTF-16编码的一个转换表,图中字符下面标注的四位16进制数字即是UTF-16编码

1. 上面部分是兼容ASCII单字节编码。

2. 下面阴影部分是双字节编码中的第一个字节,表中作为超链接,可以点击进去查看具体内容。

注:0x80(=128)被用于欧元符。(图中小圆框部分)0xFF则保留,实际共有128-2=126块。

另:新的GB18030标准使用双字节编码欧元符号,去掉了这个单字节编码。

第二字节

前面说到“啊”的机内码是B0A1,我们点击B0(上图中红色小框部分)去查看一下(来自http://msdn.microsoft.com/en-US/goglobal/gg675356):

image

1. “啊”位于A1处,所以它是兼容GB2312的。而前面的那些字符就是GBK扩展的了。

“啊”下面的554A即是它的UTF-16编码。GBK与UTF-16之间编码的转换只能通过查表实现。

2. 第二字节从0x40开始,不是从0x00也不是从0x80开始。表格只有12行。

因为不是从0x80开始,这意味着第二字节最高位也可能是0.这点与GB2312不同,GB2312确保了无论是高低字节最高位均是1。

3. 另外0x7F和0xFF两处保留未定义。

所以实际有12×16-2=192-2=190个字符。注:并非所有的块里面都是190个字符,也有不少是少于190的。

粗略估算可得126×190=23940,所以GBK也就是两万多个字符这样子。

GBK还是UTF-8?

GBK使用两字节保存中文,也能兼容ASCII,而对常用汉字,UTF-8都是采用三字节编码,因此无论是全中文还是中英文混合的情况,GBK保存的效率都要好于UTF-8.

这也不奇怪,毕竟是亲生的。

但它也有些不好的地方,比如它不能支持一些国际性的文字,在国际化,通用性方面它肯定不如UTF-8;就汉字而言,由于容量空间的限制,它也无法收录更多的汉字了。

所以,怎么选择,自己看着办。

GB18030


GB18030前后发布了两个标准,最新的是2005年发布的GB 18030-2005(《信息技术 中文编码字符集》),2000年还有一版GB18030-2000,更多了解可参考百度百科http://baike.baidu.com/view/889058.htm,官网见http://gbread.sac.gov.cn/bzzyReadWebApp/standardresources.action?m=readFile&bzNum=GB 18030-2005&flag=1,(注:这个文件比较大)

对于多数用户而言,无需了解太多,这里也不打算详细介绍,下面是一些简介(针对最新的GB18030-2005):

  1. 它也是一个多字节编码方案,有一,二,四字节三种变长组合。

  2. 它的编码空间很大,高达160万(约数),这甚至超过了Unicode规定的110万(约数)。

  3. 它兼容GB2312,基本兼容GBK(只有很少几处不同)。

  4. 它收录高达7万多的汉字,Unicode中的CJK统一汉字,CJK统一汉字扩充A,CJK统一汉字扩充B均收录了进来。

  5. 它还支持许多少数民族如藏、蒙古、彝、维吾尔等的文字。

对于普通用户,超大字符集很少用到,通常情况下,如Windows系统下你可能要安装GB18030的相关插件才能处理及显示那些增补的字符,一般系统默认情况也不会安装能支持完整显示GB18030全体字符的字体。

GB18030作为一个强制标准,但由于采用了高达四字节的情形,无论是操作系统还是各种应用软件,可能涉及许多调整才能很好地支持,这决不是一件简单的事情。

作为国际性标准的Unicode,BMP以外的字符的处理与显示都还有很多不完善,所以如果GB18030没有得到很好的支持,那也不足为奇了。

关于GB系列的编码就说到这里。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!