字符编码详解:UTF-8、Unicode、GBK 等

编辑说明:本文初稿完成后,通过 Claude Code 进行了技术审阅和优化润色,修正了 UTF-16 编码方式的描述,补充了 Unicode 平面结构、代理对机制、MySQL utf8mb4 等深度内容,使文章在保持通俗易懂的同时更加准确严谨。


字符编码详解:UTF-8、Unicode、GBK 等

一、核心概念:Unicode vs UTF-8

用快递系统来理解

Unicode(统一字符集)

  • 相当于:全国统一的地址编号系统
  • 作用:给世界上每个文字、符号分配唯一编号
  • 例子:
    • ‘A’ → U+0041(编号 65)
    • ‘中’ → U+4E2D(编号 20013)
    • ‘😊’ → U+1F60A(编号 128522)
  • 特点:只定义编号,不管怎么存储

UTF-8(编码方式)

UTF-8 根据 Unicode 码点的大小,使用不同长度的字节:

Unicode 范围 字节数 UTF-8 格式
U+0000 ~ U+007F 1 字节 0xxxxxxx
U+0080 ~ U+07FF 2 字节 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 3 字节 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 4 字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 相当于:快递打包和运输方式
  • 作用:把 Unicode 编号转换成实际的字节数据
  • 例子:
    • ‘A’ (U+0041) → 01000001(1 字节)
    • ‘中’ (U+4E2D) → 11100100 10111000 10101101(3 字节)
    • ‘😊’ (U+1F60A) → 11110000 10011111 10011000 10001010(4 字节)
  • 特点:变长编码,节省空间

上面的例子详解:

  1. 固定的前缀:告诉解码器这是几字节的字符

    • 0 = 1 字节
    • 110 = 2 字节
    • 1110 = 3 字节
    • 11110 = 4 字节
  2. 后续字节都以 10 开头:便于识别和同步

  3. x 的位置:填入 Unicode 码点的二进制数据

例 1:’A’ (U+0041)

  • Unicode: U+0041 = 十进制 65
  • 范围判断:65 < 128,使用 1 字节
  • 二进制:01000001
  • 套用格式 0xxxxxxx:直接就是 01000001

例 2:’中’ (U+4E2D)

  • Unicode: U+4E2D = 十六进制 4E2D = 二进制 0100111000101101
  • 范围判断:4E2D 在 0800-FFFF 之间,使用 3 字节
  • 格式:1110xxxx 10xxxxxx 10xxxxxx(共 16 个 x)

填充步骤:

1
2
3
4
原始二进制:0100 111000 101101
↓ ↓ ↓
填入格式: 1110|0100 10|111000 10|101101
结果: 11100100 10111000 10101101

例 3:’😊’ (U+1F60A)

  • Unicode: U+1F60A = 二进制 000011111011000001010(21 位)
  • 范围判断:1F60A > 10000,使用 4 字节
  • 格式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(共 21 个 x)

填充步骤:

1
2
3
4
原始二进制:000 011111 011000 001010
↓ ↓ ↓ ↓
填入格式: 11110|000 10|011111 10|011000 10|001010
结果: 11110000 10011111 10011000 10001010

关系总结

1
2
Unicode = 字典(定义每个字是什么编号)
UTF-8 = 书写方式(如何用字节把编号写下来)

二、UTF-8 的优势

为什么 UTF-8 最流行?

  1. 节省空间

    • 英文字母:1 字节(和 ASCII 一样)
    • 中文汉字:3 字节(够用)
    • 复杂符号:4 字节(罕见)
  2. 兼容 ASCII

    • 前 128 个字符完全一样
    • 老程序无需修改就能用
  3. 容错性好

    • 损坏一个字节不会影响其他字符
    • UTF-16/UTF-32 字节损坏后可能导致多个字符解析错误

三、其他 Unicode 编码方式

UTF-16

  • 常用字符 2 字节,emoji 等特殊符号 4 字节
  • 使用场景:Windows 内部、Java 字符串
  • 优点:处理中文等字符效率较高
  • 缺点:不兼容 ASCII,浪费空间

🔍 深入理解:为什么 UTF-16 有时 2 字节有时 4 字节?

这涉及 Unicode 的”平面”(Plane)概念:

Unicode 的分区结构

1
2
Unicode 字符空间 = 17 个平面 × 每个平面 65536 个字符
总共可以表示 1,114,112 个字符

1. BMP 基本多文种平面(Basic Multilingual Plane)

  • 范围:U+0000 到 U+FFFF(0 到 65535)
  • 包含内容
    • 英文字母、数字、标点
    • 中日韩常用汉字(CJK)
    • 阿拉伯文、西里尔文等各国文字
    • 常用符号(©、®、™ 等)
  • UTF-16 编码:直接用 2 字节表示
  • 覆盖率:99% 的日常使用字符都在这里

2. 补充平面(Supplementary Planes)

  • 范围:U+10000 到 U+10FFFF(65536 到 1,114,111)
  • 包含内容
    • Emoji 表情:😊 🚀 🎉
    • 罕见汉字:𠮷(吉的异体字)、𩸽(鱼名)
    • 古代文字:𒀀(楔形文字)、𓀀(埃及圣书体)
    • 数学符号:𝕏(双线体字母)
  • UTF-16 编码:用”代理对”(Surrogate Pair)表示

什么是代理对?

因为补充平面的字符编号超过了 65535,2 字节装不下,UTF-16 用了一个巧妙方法:

1
2
3
4
5
代理对 = 高代理(High Surrogate)+ 低代理(Low Surrogate)
↓ ↓
2 字节 2 字节

总共 4 字节来表示一个补充平面的字符

实际例子

1
2
3
4
字符:😊(笑脸 emoji)
Unicode 编号:U+1F60A(在补充平面)
UTF-16 编码:0xD83D 0xDE0A(代理对,共 4 字节)
↑高代理 ↑低代理

为什么这么设计?

  • 当年设计 UTF-16 时,以为 65536 个字符够用了
  • 后来发现不够(emoji、古文字不断增加)
  • 为了向后兼容,只能用代理对这个”补丁”方案

UTF-32

  • 固定 4 字节,直接存储 Unicode 码点值
  • 使用场景:内部处理(很少用于存储)
  • 优点:每个字符长度一致,索引方便
  • 缺点:极度浪费空间

对比表

字符 Unicode 编号 UTF-8 UTF-16 UTF-32
A U+0041 1 字节 2 字节 4 字节
U+4E2D 3 字节 2 字节 4 字节
😊 U+1F60A 4 字节 4 字节 4 字节

说明:UTF-8 对英文友好,UTF-16 对中文友好,UTF-32 对所有字符等长但浪费空间


四、历史遗留:GBK、GB2312 等

背景故事

在 Unicode 普及之前,各国都有自己的编码标准:

  • 中国:GB2312、GBK、GB18030
  • 日本:Shift-JIS
  • 韩国:EUC-KR
  • 台湾:Big5

中文编码演进

1. GB2312(1980 年)

  • 收录:6763 个汉字 + 682 个符号
  • 范围:常用简体中文
  • 编码:2 字节(定长)
  • 缺点:繁体字、生僻字都没有

2. GBK(1995 年)

  • 收录:21003 个汉字
  • 范围:简体 + 繁体 + 生僻字
  • 编码:2 字节(定长)
  • 特点:向下兼容 GB2312
  • 缺点:不是国际标准,只能表示中文

3. GB18030(2000 年)

  • 收录:70244 个汉字
  • 范围:中日韩统一汉字 + 少数民族文字
  • 编码:1/2/4 字节(变长)
  • 特点:国家强制标准,兼容 GBK
  • 现状:逐渐被 UTF-8 取代

GBK vs UTF-8 对比

特性 GBK UTF-8
国际通用性 ❌ 只用于中文 ✅ 全球标准
中文效率 ✅ 2 字节 ⚠️ 3 字节
英文效率 ❌ 2 字节 ✅ 1 字节
兼容 ASCII ❌ 不兼容 ✅ 完全兼容
emoji 支持 ❌ 不支持 ✅ 支持

五、实际应用场景

什么时候用 UTF-8?

  • ✅ 网页开发(HTML/CSS/JS)
  • ✅ 数据库存储(MySQL、PostgreSQL)
  • ✅ API 接口(JSON/XML)
  • ✅ 跨平台文件(Linux/Mac/Windows)
  • ✅ 开源项目(国际协作)

什么时候可能遇到 GBK?

  • ⚠️ 老旧 Windows 系统(中文版)
  • ⚠️ 某些政府/银行系统
  • ⚠️ 老式数据库导出文件
  • ⚠️ 古老的文本文件

编码混乱的典型问题

乱码示例

1
2
3
原文:你好世界
UTF-8 误读为 GBK:浣犲ソ涓栫晫
GBK 误读为 UTF-8:���������

原因:写入时用 UTF-8,读取时用 GBK(或反过来)

解决:统一使用 UTF-8,在文件头声明编码

1
2
<!-- HTML -->
<meta charset="UTF-8" />
1
2
# Python
# -*- coding: utf-8 -*-

六、快速判断指南

遇到乱码怎么办?

  1. 检查文件编码

    1
    2
    3
    4
    # Linux/Mac
    file -i 文件名.txt

    # 显示: charset=utf-8 或 charset=iso-8859-1
  2. 转换编码

    1
    2
    # 将 GBK 转为 UTF-8
    iconv -f GBK -t UTF-8 input.txt > output.txt
  3. Python 读取指定编码

    1
    2
    3
    4
    5
    6
    7
    # 读取 GBK 文件
    with open('file.txt', encoding='gbk') as f:
    content = f.read()

    # 保存为 UTF-8
    with open('file_utf8.txt', 'w', encoding='utf-8') as f:
    f.write(content)

七、记忆口诀

1
2
3
4
Unicode 是身份证号,给每个字编号;
UTF-8 是快递方式,决定怎么打包送;
GBK 是地方标准,只在中文区能用;
现代项目选 UTF-8,兼容全球不出错!

八、常见误区

误区 1:”UTF-8 是一种字符集”
正确:UTF-8 是编码方式,Unicode 才是字符集

误区 2:”GBK 比 UTF-8 更省空间”
正确:仅对纯中文文本成立,混合英文时 UTF-8 更优

误区 3:”Unicode 只有 UTF-8 一种编码”
正确:还有 UTF-16、UTF-32 等多种编码

误区 4:”UTF-8 每个字符都是 8 位”
正确:UTF-8 是变长编码,1-4 字节不等

误区 5:”UTF-8 文件必须有 BOM”
正确:UTF-8 不需要 BOM(字节顺序标记),加上反而可能导致问题


九、推荐做法

新项目开发

  1. 一律使用 UTF-8

    • 源代码文件:UTF-8
    • 数据库:UTF-8(MySQL 使用 utf8mb4,因为 MySQL 的 utf8 是阉割版只支持最多 3 字节)
    • 网页:<meta charset="UTF-8">
    • API 响应头:Content-Type: application/json; charset=utf-8
  2. 避免使用 GBK

    • 除非对接古老系统必须用 GBK
    • 转换时先转成 UTF-8 再处理
  3. 文本编辑器设置

    • VS Code:设置默认编码为 UTF-8

总结

  • Unicode:全球字符大字典,定义编号规则
  • UTF-8:最流行的编码方式,节省空间、兼容好
  • GBK/GB2312:历史遗留的中文编码,逐渐淘汰
  • 建议:新项目全部用 UTF-8,老项目遇到 GBK 及时转换

最终建议:如果不知道用什么编码,就用 UTF-8,99% 的情况下没问题!


更新记录

  • 2025-12-10:文章创建
  • 2025-12-10:通过 Claude Code 进行技术审阅,主要优化:
    • 修正 UTF-16 编码方式的描述(从”固定字节”改为基于 Unicode 平面的准确表述)
    • 新增 Unicode 平面结构和代理对机制的深度解析
    • 补充 MySQL utf8mb4 的详细说明
    • 新增 BOM(字节顺序标记)相关误区
    • 优化对比表说明和容错性表述
    • 保持科普风格的同时提升技术准确性