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

摘要:关于 GB2312,GBK,GB18030 编码的一些介绍,还有区位码,国标码,机内码间的转换关系。

目录
[隐藏]

前面的一些篇章更多谈论了 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 个区,每个区有 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。

gb2312_1601_0101

所谓区位码就是这一 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 即可得到机内码,如下图所示:

GB-区位码到国标码及机内码的转换

如果你新建一个文本文件,录入“啊”字,以 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 坐标中的位置的一个示意图:

GB-区位码,国标码,机内码在坐标中的位置

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 系列的编码就说到这里。

发表评论

电子邮件地址不会被公开。 必填项已用*标注