简介:GB2312标准字库.json 是一份可直接使用的标准汉字映射数据,面向前端、软件开发者以及中文信息处理相关技术人员,用于解决汉字编码查询、字符集转换、字体制作等需求。文件内部采用数组结构,每条记录由序号和汉字组成,完整收录 GB2312 标准中的常用汉字及符号,结构规整,无多余嵌套,方便解析或转存为其他常用数据格式。资源打包为 rar 压缩包,内含 1 个 json 文件,包体仅 8KB,几乎不占用存储空间,适合嵌入轻量级项目或作为离线数据源使用。目前已有 123 人学习下载,初学者可快速获取标准字库,开发者也能直接应用。借助该文件,可在汉字查询、乱码修复、文本清洗、样本构建等场景中快速建立索引;配合脚本还能实现按编码范围筛选、批量生成字表、标注拼音等二次开发,有效减少手动收集字库的繁琐工作。 上次给工业设备的显示面板做离线字库,客户需求文档里就一行字:“GB2312标准字库.json”。我一开始以为这事儿简单,无非是把区位码表转成JSON,结果真正动手才发现,网上能找到的“GB2312字库JSON”大多有毛病:有的只有一级汉字,有的把区位码和Unicode混着存,有的干脆是从某个客户端里粘出来的残缺数据。折腾一晚上之后,我决定把整条链路完整走一遍——从GB2312的编码规则,到JSON结构设计,再到实际落地场景和踩坑记录,一次性整理清楚。
这篇文章就是那次整理的成果,适合三类人看:需要在项目里维护汉字码表JSON的开发者、做离线字库或嵌入式UI的资源工程师,以及想弄明白GB2312和JSON怎么配合使用的初学者。如果你只需要一个能直接用的生成脚本,可以直接跳到第二章;如果你想知道为什么这份JSON要这么设计,建议从头读。
1. 94×94矩阵里的汉字:区位码换算与数据范围
1.1 为何整个GB2312能装进94×94的矩阵
GB2312不是简单地“给每个汉字发一个编号”,它把收录的7445个字符(6763个汉字加682个其他符号)放进了一个94行乘94列的矩阵。行叫做“区”,列叫做“位”,所以才有“区位码”这个说法。16区到55区存放一级汉字,按拼音排序;56区到87区存放二级汉字,按部首和笔画排序;01区到09区是各种符号、数字和希腊字母。为什么偏偏是94而不是100?因为在GB2312设计时,需要考虑和ASCII终端的兼容性:可打印ASCII字符是0x21到0x7E,正好94个,区号和位号也都在1到94之间,这样当时的终端设备可以按同样的逻辑逐字打印。
这个矩阵结构决定了GB2312的边界非常清晰:不是所有汉字都在里面,甚至不是所有常用汉字都在里面。比如“镕”“喆”这类字,就只能在GBK或GB18030里找到。所以在做字库JSON时,第一个要确定的事就是:你要的是标准GB2312的6763个汉字,还是包含了扩展字符的GBK全集。这两者的JSON结构和生成逻辑完全不同,后面我会专门讲这个坑。
1.2 区位码、国标码、机内码的三级换算
理解了矩阵之后,接下来就是三个绕不开的概念:区位码、国标码、机内码。它们之间的换算关系是固定的,往JSON里存哪一层,取决于你的消费端需要什么。
- 区位码是人读的视角,比如“啊”在16区01位,通常写作“1601”。
- 国标码是区位码的十进制数转十六进制后,各加0x20得到的。0x10加0x20等于0x30,0x01加0x20等于0x21,所以“啊”的国标码是0x3021。
- 机内码是实际在计算机里存储、传输的两字节值,等于国标码再各加0x80,也就是区位码各加0xA0。“啊”的机内码就是0xB0A1。
下面用一张表把“啊”“中”“国”三个字的换算过程列清楚:
| 字符 | 区位码 | 国标码 | 机内码 |
|---|---|---|---|
| 啊 | 16-01 | 0x3021 | 0xB0A1 |
| 中 | 54-48 | 0x5650 | 0xD6D0 |
| 国 | 25-90 | 0x397A | 0xB9FA |
看到规律了吗?两个字节的高位和低位各自独立参与运算,所以区位码、国标码、机内码本质上是同一信息的三套视图。做JSON时没必要三套都塞进去,但至少需要保留两套:一套给人读(区位码),一套给程序用(机内码或Unicode)。Unicode值得单列,因为它是跨平台、跨语言的桥梁,JSON本身又是Unicode友好的格式,这样可以省去使用者自己去换算的麻烦。
2. 从空字符串到6763个汉字:JSON生成全流程
2.1 用Python遍历码位而不是手工整理码表
一开始我试图在网上找一个“标准码表”,然后导入Excel整理成JSON。事实证明这个路子极其低效,因为不同来源的码表格式五花八门,有的只给机内码,有的给的是区位码文本,有的甚至把两个字节写反了。最可靠的生成方式是用编程语言直接计算:GB2312的编码规则是公开的,那么遍历94×94个码位,把每个码位的两字节序列用gb2312解码,能解码成功的就加入字库,解码不了的说明是空位,跳过。
下面这个Python脚本是整套流程的核心:
import json def classify(qu): if 1 <= qu <= 9: return "symbol" if 16 <= qu <= 55: return "level1" if 56 <= qu <= 87: return "level2" return "reserved" def generate_gb2312_json(): entries = [] for qu in range(1, 95): for wei in range(1, 95): hi = qu + 0xA0 lo = wei + 0xA0 try: ch = bytes([hi, lo]).decode('gb2312') except UnicodeDecodeError: continue entries.append({ "qu": qu, "wei": wei, "gb2312_hex": "0x{:02X}{:02X}".format(hi, lo), "unicode_hex": "0x{:04X}".format(ord(ch)), "char": ch, "category": classify(qu) }) return {"chars": entries} if __name__ == "__main__": data = generate_gb2312_json() with open("gb2312_standard.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) print("total:", len(data["chars"]))这里的核心逻辑就是拿每个区号和位号分别加上0xA0组成机内码,然后交给Python自带的gb2312解码器。解码器本身是经过校验的,所以步骤很干净。注意循环范围,range(1, 95)意味着区号和位号从1遍历到94,一个不多一个不少——这一点看起来微不足道,但恰恰是最容易出错的地方,我在第四章展开。
2.2 JSON字段设计:既要好读也要好查
字段怎么设计,直接决定了这份JSON在项目里好不好用。我见过两种极端做法:一种只存一个字符串数组,比如["啊","阿","埃"...],看着简洁,但使用者一旦需要区位码或者Unicode,就得再拿一份码表做二次关联;另一种是把所有可能的编码元数据全塞进去,一条记录二十多个字段,文件体积大、可读性也差。
我比较推荐的字段是上面脚本里的六个:qu、wei、gb2312_hex、unicode_hex、char、category。其中category是对汉字分区的标记,它的价值在拼音索引和字体子集裁剪场景里会体现出来。如果项目对大小敏感,可以去掉unicode_hex,因为JSON本身支持Unicode字符,char字段已经把信息带出来了;如果想追求最快的查找速度,可以在文件里额外生成一个以gb2312_hex为键的对象索引,代价是文件体积上涨,取舍取决于你的消费端。
给一个示例输出片段,就是“啊”这条记录:
{ "qu": 16, "wei": 1, "gb2312_hex": "0xB0A1", "unicode_hex": "0x554A", "char": "啊", "category": "level1" }2.3 五条自校验规则,跑完才算生成完成
生成脚本能跑出结果,并不代表结果就是正确的。我建议每次生成后都跑一遍自校验,把这五条规则写进同一个脚本:
- 汉字总数必须等于6763。
- level1(一级汉字)数量必须等于3755,level2(二级汉字)数量必须等于3008。
- 一级汉字所在区必须在16到55之间,二级汉字必须在56到87之间。
- 随机抽几个已知字符做双向验证,比如“啊”的机内码应该是0xB0A1,“中”是0xD6D0,“国”是0xB9FA。
- 全表不能出现重复的Unicode码点。
第五条看着多余,但真的会发生——GB2312的符号区里有不少码位在Unicode中没有独立对应字符,解码器可能用替换符或统一字符兜底,导致两个不同码位映射到同一个Unicode码点。如果不是刻意查重,这种数据错误很难发现。把上面五个校验项写进一个校验函数,每次重新生成码表或者调整字段结构以后直接跑一遍,能省下大量的下游排查时间。
3. 字库JSON在真实项目中的落地方式
3.1 拼音分组索引表
GB2312的一级汉字是按拼音排序的,这个特性让“GB2312拼音索引表”变得非常实用。在只需要全拼首字母索引、不需要导入第三方拼音库的场景下,直接按区位码顺序遍历一级汉字,就能把字母A到Z的汉字分组切出来。因为同一拼音的汉字在区位码表里通常是连续排列的,相邻两个字的拼音发生变化时,就是切分点。
拿生成好的JSON做分组的逻辑很简单:加载chars数组,过滤出category等于level1的条目,按照qu和wei排序,然后设定一个拼音变化的判断。判断拼音变化有几种做法,最粗暴的是调用拼音库逐个字标注,但那就绕回了“不想引入依赖”的初衷;更好用的是查一个精简对照表,把3755个一级汉字按拼音分组的边界位置记录下来。这个边界表的每个拼音最后一个字是固定的,你只要从GB2312码表里提取一次,就能存成一份独立的小JSON,以后每次做索引都不用再跑拼音库。
3.2 字体子集裁剪时的字符清单
前端网页或者嵌入式GUI嵌入中文字体时,最头疼的是字体文件太大。一套完整的中文字体动辄几兆到几十兆,但实际业务里往往只需要几百个字。这时候GB2312字库JSON就派上了用场:把需要的字符从JSON里过滤出来,写入一个纯文本chars.txt,再交给字体裁剪工具处理。
以Python生态里的pyftsubset为例,命令长这样:
pyftsubset NotoSansSC-Regular.otf --text-file=chars.txt --output-file=NotoSansSC-subset.woff2在JSON里一行代码就能拼出chars.txt:
with open("chars.txt", "w", encoding="utf-8") as f: for item in data["chars"]: f.write(item["char"])实际项目中我会在过滤时加上category判断。比如一个数据展示大屏只显示一级汉字和数字,那就在循环里把category为symbol和level1的字符挑出来,再额外加上业务方提供的生僻字列表。这样生成的subset字体体积能减少70%以上,页面加载速度提升非常明显。
3.3 嵌入式离线查表的数据源
嵌入式设备上没有操作系统的字符集表,处理中文显示时通常要自己做查表。把GB2312字库JSON转成C语言可用的查表数组,是这类项目里最常见的诉求。思路是把JSON里的gb2312_hex作为索引,把字符对应的点阵数据或渲染索引作为值,生成一个静态const数组。
在转C数组时有一个常见选择:是按区位码顺序排一维数组,还是做二维数组。我推荐后者,原因很朴素——GB2312本身就是94×94的矩阵,按区做外层、位做内层的二维数组,代码可读性最高,也方便做区间判断。伪代码示意:
typedef struct { uint16_t gb2312_code; uint16_t unicode; const uint8_t* bitmap; } gb_char_t; gb_char_t gb2312_table[94][94] = { ... };结构体数组的好处是,即使某个位置是空位,也能通过gb2312_code是否为0来判断。而且由于JSON生成过程已经帮我们确认了码位合法性,C端不用再做多余校验,直接按索引访问就行。注意,如果目标芯片Flash极小,建议只保留你实际用到的连续区段,不要一次性把94×94全量塞进去。
4. 生成与使用中的三个高频坑
4.1 自称GB2312的文件,实际可能是GBK
这个坑我踩得最实。第一次拿到客户给的参考JSON时,我数了一遍,汉字数量远超6763,心里就有数了——这份表是GBK或者GB18030的子集,只是文件名写着“GB2312”。GBK是GB2312的扩展,汉字的编码范围更大,凡是GB2312能编的GBK都能编,反过来就不行。如果你拿着GBK的码表去做严格GB2312的校验,数量对不上,查表也必然错位。
判断一份JSON到底是GB2312还是GBK,方法很简单:看它里面的gb2312_hex字段,如果字节值大量出现在0x8140到0xA0FE以及0xAA40到0xFEFE这些扩展区段,那基本就是GBK。如果你需要的是严格标准GB2312,就直接用第二章的脚本重新生成,别在来源不明的码表上浪费时间。日常办公软件里那些“声称支持GB2312但实际只支持GBK”的乱码问题,根源也大多在这里。
4.2 区号从1开始还是从0开始
写遍历循环的时候,如果你习惯性写了range(0, 94),生成出来的第一行会是“0区0位”这种不存在的码位,后续所有码位整体偏移一个区。这种错误最阴险的地方在于:字库依然能解码出汉字,只是每个字的区位码全都错位,而且因为GB2312每个区的字符分布并不均匀,有些位置的错位不会立刻暴露,直到下游做拼音分组时才发现字母索引全乱了。
所以我会在生成脚本里加一个强制断言:
first = data["chars"][0] assert first["qu"] == 1 and first["wei"] == 1, "区位码起点错误"另外一个非常容易忽略的细节是:即使循环起点对了,也要检查第一个汉字是不是“啊”。因为16区01位是“啊”,如果你的生成结果第一条是其他字,要么是区号范围写错,要么是解码顺序被排序函数打乱了。
4.3 编码成功不等于属于GB2312
最后一个坑与编程语言的行为有关。在Python里,对一个字符串调用encode('gb2312'),如果编码器发现某个字符不在GB2312范围,会因为strict模式抛异常;但如果你用的是gb2312别名,或者个别环境配置了errors='replace',它可能会悄悄用GBK甚至GB18030的方式处理,导致你产出的“GB2312 JSON”里混入了不该存在的字符。
更隐蔽的是,有些解码器在解码符号区时,会把多个不同码位映射到同一个Unicode字符。我的建议是:不要只看encode能不能成功,要回到区位码矩阵本身判断。严格的做法就是前面脚本里那样,直接构造机内码字节流让解码器去按GB2312解开,这样能成功解码的码位,才是真正意义上的GB2312字库成员。
在项目里我最后保留了一条兜底规则:凡是category等于level1或level2的汉字记录,unicode_hex必须落在0x4E00到0x9FA5这个CJK统一表意文字基本区范围内。这个范围覆盖了GB2312的全部6763个汉字,只要出现范围外的记录,不用看内容就能判断数据混入了符号或GBK扩展字符。
做完这套流程,我现在生成字库JSON的习惯已经完全固定下来:脚本、码表版本、校验日志一起放进仓库,每次重新生成都先跑assert。后面如果你也要做同样的字库,我的建议是——先确认消费端要的是“严格GB2312”还是“GBK之上的兼容子集”,然后再跑生成脚本,数据只要过一遍那五条校验规则,基本就稳了。这个流程我后来又用于生成GBK、GB18030的码表JSON,思路完全一样,只是解码器和预期数量改一改而已。
本文还有配套的精品资源,点击获取