Bootstrap

字符集与编码(九) 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(比标准方案多增加了一些字符)

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

gb2312 字符集概览图

它是一个 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 个汉字.

    一级汉字与二级汉字:

    gb2312 一级汉字与二级汉字

    第 16-55 区: 一级汉字, 3755 个(以拼音字母排序)

    第 56-87 区: 二级汉字, 3008 个(以部首笔画排序)

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

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

    image

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

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

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

    2. 各区的一个具体情况:

    • 第 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 为低字节. 如下:

gb2312 区位码示意

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), 再用十六进制查看, 你会发现使用的是机内码:

nodepad 显示 gb2312 机内码

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

@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 个字节:

gb2312 英文, 半角及全角字符机内码

我们说 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

gbk 首字节

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 ):

gbk 第二字节 B0

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

;