你可能在旧文件或某些网站上见过像 锟斤拷 或 ä½ å¥½ 这样的乱码。这就是 mojibake ——字符编码不匹配的结果。理解计算机如何存储和解释文本对每个开发者都至关重要。本指南涵盖从 ASCII 到 UTF-8 的演变、常见陷阱以及一个实际示例,帮助你掌握编码知识。

什么是字符编码?
计算机只理解二进制——0 和 1。要显示字母 A 或汉字 中,机器需要在人类可读的符号和二进制序列之间建立映射。这种映射就是 字符编码。
字符集 为每个字符分配一个唯一的数字(码点)。例如,在 Unicode 中,A 是 U+0041,中 是 U+4E2D。编码 随后将该码点转换为特定的字节序列。UTF-8、UTF-16 和 GBK 都是实现不同字符集的编码。
编码简史
ASCII:美国标准
ASCII(美国信息交换标准代码)于 1963 年推出。它使用 7 位表示 128 个字符:英文字母、数字、标点符号和控制码。每个值低于 128 的字节都是 ASCII 字符。
| 范围(十进制) | 类别 | 示例 |
|---|---|---|
| 0–31 | 控制字符 | 空字符(0),换行符(10) |
| 32 | 空格 | |
| 48–57 | 数字 | 0–9 |
| 65–90 | 大写字母 | A–Z |
| 97–122 | 小写字母 | a–z |
关键事实:'A' = 65,'a' = 97。差值为 32 —— 对大小写转换很有用。
GBK:中文字符编码
ASCII 无法表示汉字。20 世纪 80 年代,中国开发了 GB2312(每个汉字 2 字节,6763 个字符)。后来,GBK(国标扩展)将其扩展到 21003 个字符,包括繁体中文。GBK 多年来一直是中文 Windows 系统上的默认 ANSI 编码。
Unicode:一统江湖
Unicode 为每种书写系统中的每个字符提供了唯一的码点——目前超过 14 万个字符。编码空间范围从 U+0000 到 U+10FFFF(1,114,112 个可能的码点)。Unicode 本身只是一个字符集;它没有规定如何将码点存储为字节。
UTF-8:主流编码
UTF-8 是 Unicode 的一种变长编码。每个码点使用 1 到 4 个字节:
| 码点范围 | 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) |
UTF-8 向后兼容 ASCII:任何 ASCII 文本都是有效的 UTF-8。它对拉丁文字节省空间,并且具有自同步能力(丢失一个字节只会损坏当前码点)。如今,超过 98% 的网页使用 UTF-8。
常见陷阱
- 乱码:以 UTF-8 打开 GBK 编码的文件会产生像
锟斤拷这样的乱码。解决方法:始终知道原始编码并使用它解码。 - BOM(字节顺序标记):某些 UTF-8 文件以
\xEF\xBB\xBF开头。这个 BOM 会混淆解析器(例如 JSON)。首选无 BOM 的 UTF-8。 - 数据库编码:MySQL 的
utf8字符集只支持最多 3 字节的 UTF-8,缺少 emoji。请改用utf8mb4。 - 字符串长度:JavaScript 中的
String.length计算的是 UTF-16 代码单元,而不是码点或字素簇。像 😀 这样的 emoji 是 2 个代码单元(代理对)。对于面向用户的长度,请使用Intl.Segmenter或库。 - 规范化:Unicode 允许某些字符有多个表示形式(例如
é可以是 U+00E9 或 U+0065 U+0301)。在比较字符串之前先进行规范化(NFC)。
实际示例:解码乱码文件
假设你有一个用 GBK 编码保存的文件 data.txt,但你在 UTF-8 终端中打开它。你会看到:
ä½ å¥½ ä¸ç
这是 你好世界 的 GBK 字节被解释为 UTF-8 的结果。让我们逐步修复:
- 将文件作为字节读取。
- 使用 GBK 解码这些字节。
- 将结果重新编码为 UTF-8 以供现代使用。
在 Python 中:
# 从文件中读取原始字节
with open('data.txt', 'rb') as f:
raw_bytes = f.read()
# 使用原始编码(GBK)解码
text = raw_bytes.decode('gbk')
print(text) # 输出:你好世界
# 重新编码为 UTF-8 以供现代存储
utf8_bytes = text.encode('utf-8')
with open('data_utf8.txt', 'wb') as f:
f.write(utf8_bytes)
如果你不知道原始编码,可以尝试常见的编码(GBK、Shift-JIS、ISO-8859-1)并检查输出。像 chardet 这样的工具可以帮助猜测。
何时使用哪种编码?
| 场景 | 推荐编码 |
|---|---|
| 网页、API、JSON | UTF-8(无 BOM) |
| 遗留中文文本文件 | GBK(如果原始) |
| Windows 系统 API(历史) | UTF-16 |
| 内部处理 | UTF-8 或 UTF-32 |
| 数据库 | utf8mb4(MySQL),NVARCHAR(SQL Server) |
常见问题
Unicode 和 UTF-8 有什么区别?
Unicode 是一个 字符集,为每个字符分配一个唯一的数字(码点)。UTF-8 是一种 编码,将这些数字序列化为字节。Unicode 定义了“是什么”,UTF-8 定义了“如何存储”。
为什么我会看到 锟斤拷?
这是一个经典的乱码链:以 GBK 编码的文件被解码为 UTF-8,产生了替换字符(\uFFFD),然后这些字符被重新编码并再次以 GBK 解码。结果是重复的字符 锟(0xEFBFBD) 和 斤(0xEFBFBD)。
如何将文件从 GBK 转换为 UTF-8?
在 Linux/macOS 上使用 iconv -f gbk -t utf-8 input.txt > output.txt,或使用上面实际示例中的 Python 方法。
我应该使用带 BOM 还是不带 BOM 的 UTF-8?
在大多数情况下(网页、JSON、Unix)使用不带 BOM 的 UTF-8。如果需要在遗留 Windows 工具(如记事本)中区分 UTF-8 和其他编码,则使用带 BOM 的 UTF-8。
MySQL 中的 utf8 和 utf8mb4 有什么区别?
MySQL 中的 utf8 是 utf8mb3 的别名,只支持最多 3 字节的 UTF-8 字符(BMP)。它无法存储 emoji 或 U+FFFF 以上的字符。utf8mb4 支持完整的 4 字节 UTF-8 范围。
总结
- 字符编码将字符映射到字节。编码不匹配会导致乱码。
- ASCII 是基础(7 位,128 个字符)。
- GBK 处理中文文本,但不通用。
- Unicode 提供了通用字符集;UTF-8 是主流编码。
- 始终知道你的编码,首选无 BOM 的 UTF-8,并在 MySQL 中使用
utf8mb4。 - 要探索 Unicode 编码的实践工具,请尝试我们的 文本转 Unicode 转换器。