简介GB2312标准字库.json 是一份面向中文信息处理、字体开发与字符集研究场景的结构化数据文件适合需要按编码顺序检索汉字、构建字表映射或做字符集校验的开发者与学习者使用。资源以 JSON 格式组织每个条目包含序号与对应汉字便于程序直接解析、遍历与二次加工可服务于输入法词库整理、字库比对、编码转换测试等任务。压缩包内共 1 个文件为单个 json 数据文件整体约 8KB体量轻巧解压后即可读取无需额外依赖。目前已有 123 人学习下载说明其在字符集相关需求中具备一定参考价值。读者可借此获得一份可直接加载的 GB2312 汉字样本数据用于快速验证编码映射逻辑、搭建字库索引或作为教学演示素材减少自行整理字表的时间成本。1. 从一份 GB2312 标准字库 JSON 说起谁在用它怎么用做嵌入式 UI、点阵屏驱动或者老式打印机的朋友大概率都遇到过同一个问题字库文件要么是二进制 .bin要么是厂商私有的 .dat想查一个字的点阵数据得先写个解析器调半天还对不上偏移。最近拆到一份GB2312标准字库.json把 6763 个汉字和 682 个非汉字字符的编码、区位、点阵信息全部结构化成了 JSON直接打开就能读不用再跟二进制偏移量较劲。它解决的核心问题就一个把 GB2312 的编码映射和字形数据从黑匣子里拽出来变成人能看懂、程序能直接消费的格式。适合做嵌入式显示、字库裁剪、编码转换验证的从业者也适合刚接触字符编码想拿真实数据练手的新手。JSON 标准字库 GB 这三个词凑在一起意味着你拿到的不是一份需要逆向的二进制而是一份可以 diff、可以 grep、可以按字段筛选的文本资源。2. 拆开 JSON 看结构字段含义与编码映射逻辑2.1 顶层结构与字段定义拿到文件先别急着写代码用编辑器打开看前几百行结构基本就清楚了。常见的组织方式有两种一种是以区号为键的嵌套对象一种是以字符为键的扁平对象。这份资源用的是前者顶层按 GB2312 的 94 个区组织每个区下面挂该区的字符数组。每个字符对象包含这几个关键字段字段名含义示例值注意事项char字符本身啊UTF-8 存储直接可读gb2312GB2312 编码B0A1十六进制字符串高位在前quwei区位码[16, 1]区号和位号从 1 开始计数unicodeUnicode 码点U554A用于跨编码转换dot_matrix点阵数据00 00 ...十六进制字节串按行排列字段命名可能因版本略有差异但核心就这几项。区位码和 GB2312 编码之间的换算关系是区号 0xA0 高字节位号 0xA0 低字节。比如“啊”的区位是 16 区 1 位161601760xB011601610xA1拼起来就是 B0A1。这个换算在写解析器时是必须吃透的否则后面按区遍历会错位。2.2 点阵数据的排列方式点阵字段是最容易翻车的地方。GB2312 标准字库通常提供 16×16 点阵每个汉字占 32 字节。排列顺序一般是逐行从左到右、从上到下每行 2 字节共 16 位高位在左。但有些版本会做列行转置或者把高低字节顺序颠倒。判断方法很简单找一个对称的字比如“中”或“日”把点阵数据按假设的排列方式画出来看是否对称。如果画出来是歪的说明字节序或行列顺序需要调整。# 验证点阵排列方式以“中”字为例 # 假设 dot_matrix 字段是空格分隔的十六进制字节串 dm_hex 10 00 10 00 10 00 10 00 FE 3F 10 00 10 00 10 00 10 00 bytes_data bytes.fromhex(dm_hex.replace( , )) # 按逐行、每行2字节、高位在左的方式渲染 for row in range(16): line for col_byte in range(2): byte_val bytes_data[row * 2 col_byte] for bit in range(7, -1, -1): line █ if (byte_val bit) 1 else · print(line)这段代码的逻辑是把十六进制串转成字节数组然后按每行 2 字节、每字节 8 位的方式逐位判断1 画实心块0 画点。参数row * 2 col_byte决定了字节的读取顺序如果渲染出来是乱的需要调整这个索引方式。常见做法是先拿“一”这种简单字验证它的点阵应该是一条水平线如果渲染出来是竖线说明行列转置了。2.3 编码映射的边界哪些区是汉字哪些是符号GB2312 的 94 个区不是全部放汉字。1 到 9 区是符号、数字、拉丁字母、日文假名、希腊字母等10 到 15 区空置16 到 55 区是一级汉字按拼音排序56 到 87 区是二级汉字按部首笔画排序88 到 94 区空置。写解析器时如果无脑遍历所有区会遇到空区导致索引越界或者生成一堆空对象。正确做法是先判断区号范围只处理有效区。# 按区遍历跳过空区 valid_qu_ranges [(1, 9), (16, 55), (56, 87)] def is_valid_qu(qu): for start, end in valid_qu_ranges: if start qu end: return True return False # 假设 data 是按区组织的字典键是区号字符串 for qu_str, chars in data.items(): qu int(qu_str) if not is_valid_qu(qu): continue # 处理该区字符 for ch in chars: process(ch)valid_qu_ranges这个列表定义了有效区的起止范围is_valid_qu做闭区间判断。参数上需要注意的是有些版本会把 10 到 15 区也填上占位符这时候光靠区号判断不够还要检查字符对象是否为空或者char字段是否为空字符串。3. 把 JSON 字库塞进嵌入式项目裁剪、转换与驱动对接3.1 按需裁剪只保留项目用到的字符完整字库 JSON 动辄几 MB嵌入式 Flash 根本放不下。实际项目里都是按需裁剪只保留界面上会出现的字符。裁剪的逻辑分两步先确定字符集再从 JSON 里过滤。字符集可以来自 UI 文案文件、翻译表或者直接手写一个字符串。import json # 加载完整字库 with open(GB2312标准字库.json, r, encodingutf-8) as f: full_lib json.load(f) # 项目实际用到的字符集比如从 UI 文案里提取 needed_chars set(温度湿度时间设置确认取消返回主菜单) # 构建裁剪后的字库保持原有区结构 trimmed {} for qu_str, chars in full_lib.items(): kept [ch for ch in chars if ch.get(char) in needed_chars] if kept: trimmed[qu_str] kept # 输出裁剪结果 with open(trimmed_font.json, w, encodingutf-8) as f: json.dump(trimmed, f, ensure_asciiFalse, indent2) print(f原始字符数: {sum(len(v) for v in full_lib.values())}) print(f裁剪后字符数: {sum(len(v) for v in trimmed.values())})needed_chars是集合查找复杂度 O(1)比列表快。ensure_asciiFalse保证输出的 JSON 里汉字直接可读不转成\uXXXX。裁剪后字符数会大幅下降一个典型 UI 界面用到的汉字通常不超过 200 个加上符号和数字裁剪后体积能压到几十 KB。3.2 转成 C 数组给单片机用的紧凑格式JSON 适合人读单片机不认。下一步是把裁剪后的字库转成 C 数组直接编译进固件。转换时要决定索引方式可以按 GB2312 编码排序后二分查找也可以按 Unicode 排序。按 GB2312 编码排序的好处是查找时直接用编码做二分不需要额外的映射表。def json_to_c_array(trimmed_lib, output_path): entries [] for qu_str, chars in trimmed_lib.items(): for ch in chars: gb ch[gb2312] dm ch[dot_matrix].replace( , ) entries.append((gb, dm)) # 按 GB2312 编码排序 entries.sort(keylambda x: x[0]) with open(output_path, w, encodingutf-8) as f: f.write(// 自动生成请勿手动修改\n) f.write(#include stdint.h\n\n) f.write(typedef struct {\n) f.write( uint16_t gb_code;\n) f.write( uint8_t dot[32];\n) f.write(} font_entry_t;\n\n) f.write(fconst font_entry_t font_table[{len(entries)}] {{\n) for gb, dm in entries: gb_val int(gb, 16) dot_bytes , .join(f0x{dm[i:i2]} for i in range(0, len(dm), 2)) f.write(f {{0x{gb_val:04X}, {{{dot_bytes}}}}},\n) f.write(};\n) f.write(fconst uint32_t font_table_size {len(entries)};\n) json_to_c_array(trimmed, font_table.c)gb_code存成uint16_t因为 GB2312 编码两个字节拼起来正好 16 位。dot数组固定 32 字节对应 16×16 点阵。排序后二分查找的代码在单片机端实现查找时把目标字符的 GB2312 编码算出来然后在font_table里二分。参数上要注意int(gb, 16)把十六进制字符串转成整数0x{gb_val:04X}保证输出四位十六进制不足补零。3.3 驱动对接从编码到屏幕像素单片机端拿到font_table后显示一个字符的流程是输入字符的 GB2312 编码 → 二分查找 → 拿到 32 字节点阵 → 按屏幕的像素格式逐位写入显存。如果屏幕是单色 OLED每位对应一个像素如果是 RGB LCD需要把 1 展开成前景色、0 展开成背景色。// 二分查找字库条目 const font_entry_t* find_font(uint16_t gb_code) { int left 0, right font_table_size - 1; while (left right) { int mid (left right) / 2; if (font_table[mid].gb_code gb_code) { return font_table[mid]; } else if (font_table[mid].gb_code gb_code) { left mid 1; } else { right mid - 1; } } return NULL; // 未找到 } // 在 OLED 上绘制一个 16x16 字符 void draw_char(int x, int y, uint16_t gb_code, uint8_t color) { const font_entry_t* entry find_font(gb_code); if (!entry) return; for (int row 0; row 16; row) { for (int col_byte 0; col_byte 2; col_byte) { uint8_t byte_val entry-dot[row * 2 col_byte]; for (int bit 0; bit 8; bit) { if (byte_val (0x80 bit)) { oled_set_pixel(x col_byte * 8 bit, y row, color); } } } } }find_font是标准二分draw_char里0x80 bit实现高位在左的位提取。col_byte * 8 bit把字节索引和位索引换算成列坐标。如果屏幕坐标系原点在左上角这段代码直接可用如果原点在左下角需要把y row改成y 15 - row。4. 避坑与排查字库使用中的五个血泪教训4.1 现象渲染出来全是乱码方块原因通常有两个一是点阵数据的字节序搞反了把高位在左当成了低位在左二是编码映射对不上比如把 Unicode 当成了 GB2312 去查表。先验证字节序拿“一”字测试它的点阵应该是一条水平线。如果渲染出来是竖线把位提取的顺序反过来。再验证编码打印几个已知字符的 GB2312 编码跟在线工具对比确认编码字段的格式是十六进制字符串还是十进制整数。4.2 现象部分汉字显示正常部分显示空白原因多半是裁剪时漏掉了字符或者二分查找的排序键不一致。检查裁剪脚本里的needed_chars是否包含了所有 UI 文案注意标点符号和全角空格。再检查 C 数组生成时的排序键和单片机端查找时用的编码是否一致如果生成时按 GB2312 排序但查找时传的是 Unicode必然找不到。4.3 现象字库文件加载后内存暴涨JSON 解析后占用的内存通常是文件体积的 3 到 5 倍因为每个字符对象都有额外的字典开销。如果是在 PC 端做转换工具问题不大如果是在单片机上直接解析 JSON基本不可行。正确做法是在 PC 端完成 JSON 到 C 数组的转换单片机只加载编译后的二进制数组。常见做法是用 Python 脚本做转换把生成的.c和.h文件加入工程。4.4 现象区位码换算结果对不上GB2312 的区位码从 1 开始计数但有些版本的 JSON 里quwei字段是从 0 开始的。拿到数据后先验证找“啊”字标准区位是 16 区 1 位如果 JSON 里写的是[15, 0]说明是从 0 开始换算时需要加 1。这个坑很隐蔽因为大部分字符的偏移量一致只有边界字符会暴露问题。4.5 现象点阵数据长度不是 32 字节16×16 点阵固定 32 字节如果某个字符的dot_matrix字段长度不对要么是数据损坏要么是该版本用了非标准点阵比如 12×12 或 24×24。先统计所有字符的点阵长度分布如果大部分是 32 字节、少数不是把异常的挑出来单独处理。如果全部不是 32 字节检查文件说明里是否标注了点阵规格按实际规格调整渲染代码里的行列循环次数。5. 进阶用 JSON 字库做编码转换验证与字库比对5.1 验证 GB2312 与 Unicode 的双向转换这份 JSON 里同时有gb2312和unicode字段等于自带了一份映射表。可以用它来验证自己写的编码转换函数是否正确。思路是遍历所有字符用你的转换函数把 GB2312 转成 Unicode跟 JSON 里的unicode字段比对统计不一致的数量。import json with open(GB2312标准字库.json, r, encodingutf-8) as f: lib json.load(f) mismatch [] total 0 for qu_str, chars in lib.items(): for ch in chars: total 1 gb ch[gb2312] expected_unicode ch[unicode] # 假设你有一个 gb2312_to_unicode 函数 actual_unicode gb2312_to_unicode(gb) if actual_unicode ! expected_unicode: mismatch.append((gb, expected_unicode, actual_unicode)) print(f总计: {total}, 不一致: {len(mismatch)}) for m in mismatch[:10]: print(m)gb2312_to_unicode需要你自己实现可以用 Python 的bytes.fromhex(gb).decode(gb2312)拿到字符再转成UXXXX格式。比对结果如果不一致数量为 0说明转换函数没问题如果有少量不一致检查是否是全角/半角或者异体字导致的。5.2 比对两个字库版本的差异如果你手上有两份不同来源的 GB2312 字库 JSON可以用同样的思路做 diff找出点阵数据不一致的字符。这在排查“为什么同一个字在两块屏上显示不一样”时特别有用。def compare_fonts(lib_a, lib_b): map_a {} for qu_str, chars in lib_a.items(): for ch in chars: map_a[ch[gb2312]] ch[dot_matrix] diffs [] for qu_str, chars in lib_b.items(): for ch in chars: gb ch[gb2312] if gb in map_a and map_a[gb] ! ch[dot_matrix]: diffs.append(gb) return diffs diffs compare_fonts(lib_v1, lib_v2) print(f点阵不一致的字符数: {len(diffs)}) print(f前10个: {diffs[:10]})map_a把第一份字库转成编码到点阵的字典然后遍历第二份做比对。差异列表可以用来定位是字库版本问题还是渲染代码问题。如果差异集中在某些区可能是该区的字重或字形标准不同。5.3 一个实用技巧用 JSON 字库生成预览图调试字库时把点阵数据渲染成 PNG 预览图比在屏幕上看效率高得多。用 Python 的 PIL 库可以批量生成一屏看几十个字哪个字有问题一目了然。from PIL import Image, ImageDraw def render_char(dot_matrix_hex, scale4): dm bytes.fromhex(dot_matrix_hex.replace( , )) img Image.new(1, (16, 16), 0) pixels img.load() for row in range(16): for col_byte in range(2): byte_val dm[row * 2 col_byte] for bit in range(8): if byte_val (0x80 bit): pixels[col_byte * 8 bit, row] 1 return img.resize((16 * scale, 16 * scale), Image.NEAREST) # 生成一行预览 chars_to_preview [中, 文, 字, 库, 测, 试] imgs [] for ch in chars_to_preview: # 从字库里找到该字符的 dot_matrix dm find_dot_matrix(lib, ch) imgs.append(render_char(dm)) total_width sum(img.width for img in imgs) preview Image.new(1, (total_width, imgs[0].height), 0) x_offset 0 for img in imgs: preview.paste(img, (x_offset, 0)) x_offset img.width preview.save(preview.png)Image.new(1, ...)创建单色图pixels[x, y] 1设置白点resize用最近邻插值放大保持像素边缘锐利。find_dot_matrix需要你自己实现从 JSON 里按字符查找。生成预览图后用图片查看器打开比对着屏幕调快得多。从那以后我每次拿到新字库都先跑一遍预览图生成确认点阵排列和编码映射没问题再往工程里集成。这个习惯帮我省掉了至少三次“烧录后才发现字是歪的”的返工。希望帮到你。本文还有配套的精品资源点击获取