UTF-8 与 GBK 编码详解:从乱码到 Windows 代码页
起因:一次
hermes update脚本报错,.cmd文件里全是'慨澶嶉噸鏀?' 不是内部或外部命令这样的乱码。排查后发现是 UTF-8 编码的文件被 cmd.exe 按 GBK 解析 导致的。这篇文章把这次踩坑学到的编码知识完整总结下来。
一、先分清两个概念:字符集 vs 编码
很多人把这两个概念混在一起说,其实这是两层:
| 概念 | 回答什么问题 | 例子 |
|---|---|---|
| 字符集 (Charset) | “中"这个字排第几号? | Unicode 里"中"是U+4E2D;GBK 里它是 0xD6D0 |
| 编码 (Encoding) | 这个编号怎么存成字节? | U+4E2D 存成 3 字节E4 B8 AD(UTF-8)或 2 字节 D6 D0(GBK) |
“中"还是那个"中”,只是两套系统给它编了不同的号、用不同的方式存储。
文件里存的永远是字节,读文件时必须知道"按哪套规则解释这些字节”——这就是编码的由来。
我的理解是:字符集就像字典,收录字符并给出一个唯一标志,编码就是把这个标志转换为字节表示
二、GBK:中文世界的老编码
- 中国国家标准,从 GB2312 扩展而来,是 1990 年代 ~ 2010 年代中文电脑的默认编码
- 汉字 2 字节,ASCII 字符 1 字节
- 只覆盖中文 + ASCII(及少量日文假名、朝鲜文等),不认识其他文字
- 优点:中文省 1/3 空间;是中文 Windows 的原生编码(代码页 CP936)
三、UTF-8:全世界的通用语
- 是 Unicode 字符集的一种编码实现,目标是把全人类所有文字统一编号
- 变长编码:ASCII 1 字节、欧洲文字 2 字节、中文 3 字节、生僻字 4 字节
- 关键设计:完全兼容 ASCII——任何一个 ASCII 文本文件,字节不变就是合法的 UTF-8 文件
这就是"UTF-8 最兼容"的真正含义:它不是"和所有编码兼容",而是它本身就是国际标准,任何现代系统都按它解释。
⚠️ 但注意:UTF-8 和 GBK 之间不兼容——同一个中文,UTF-8 写 3 字节,GBK 写 2 字节,用错规则读就是乱码。
四、实测:同一个字,两种编码的字节对比
>>> '中'.encode('utf-8')
b'\xe4\xb8\xad' # 3 字节
>>> '中'.encode('gbk')
b'\xd6\xd0' # 2 字节
>>> 'A'.encode('utf-8')
b'A' # 1 字节
>>> 'A'.encode('gbk')
b'A' # 1 字节(ASCII 两者相同)
| 字符 | Unicode 码点 | UTF-8 字节 | GBK 字节 |
|---|---|---|---|
| 中 | U+4E2D | E4 B8 AD(3B) |
D6 D0(2B) |
| A | U+0041 | 41(1B) |
41(1B) |
五、乱码是怎么产生的
今天脚本报错的"重放"二字,UTF-8 编码是 6 字节:E9 87 8D E6 94 BE
写入时 (UTF-8): E9 87 8D E6 94 BE → 6 字节
读取时 (GBK): E9 87 | 8D E6 | 94 BE → 每 2 字节拼成一个"字"
结果: "閲嶆斁" ← 完全不同的字
更糟的是,GBK 按 2 字节分组时,某些字节组合恰好落在 & | < > 等命令分隔符的字节值上,cmd.exe 就把整行当命令执行了——这就是 '慨澶嶉噸鏀?' 不是内部或外部命令 的来源。
乱码的本质是"解释规则用错了"。 同一个文件,用 UTF-8 读是对的,用 GBK 读就是一堆莫名其妙的字。
用 Python 可以完整还原这个过程:
word = '重放'
u8b = word.encode('utf-8')
print(u8b) # b'\xe9\x87\x8d\xe6\x94\xbe'
print(u8b.decode('gbk')) # 閲嶆斁 ← 乱码现场
六、Windows 上的特殊性(重点)
Windows 和 Linux/macOS 最大的不同:Windows 有"ANSI 代码页"这个历史包袱。
| Windows | Linux / macOS | |
|---|---|---|
| 系统默认编码 | 看区域设置:中文系统 ANSI =GBK (CP936) | 一律 UTF-8 |
| cmd.exe / 旧记事本 | 默认按 ANSI 读写 | — |
| 为什么容易乱码 | 文件是 UTF-8 但工具按 GBK 读 → 乱码 | 大家都 UTF-8,不乱 |
中文 Windows 上的几条铁律:
- 老工具(cmd.exe、旧记事本、旧软件)默认按 GBK 解释文件——UTF-8 文件没标记的话必乱
- UTF-8 BOM 是给 Windows 的"我是 UTF-8"标记——文件开头加
EF BB BF三个字节,Windows 记事本看到就按 UTF-8 读(但 Linux 工具反而讨厌 BOM) - cmd.exe 解析批处理时不跳过 BOM——第一行
@echo off会变成@echo off直接报错(BOM 不能乱加) chcp 65001是 cmd 的"切换语言"命令——放在批处理开头,之后的输出就按 UTF-8 解释
实战:一个能用的 UTF-8 中文批处理
@echo off
chcp 65001 >nul
echo 中文正常显示
要点:无 BOM + CRLF 换行 + 前几行纯 ASCII(@echo off/chcp 65001)+ 中文放在 chcp 之后。
为什么这里不用 PowerShell?
同样是编码问题,PowerShell 完胜 cmd:PowerShell 7 原生 UTF-8 无 BOM,5.1 认 UTF-8 BOM,根本没有 cmd 那种 GBK 字节错位解析。cmd 唯一的优势是"双击就能跑、零配置"(PowerShell 默认执行策略禁止运行 .ps1)。成熟做法是 一行 .cmd 启动器 + .ps1 主脚本:
@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0script.ps1"
七、实际选择:什么时候用哪个?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 代码文件、跨平台、发给别人 | UTF-8(无 BOM) | 现代标准,任何平台无歧义 |
Windows 批处理.cmd、INI 老配置 |
GBK 或 UTF-8 + 技巧 | cmd 默认 GBK;用 UTF-8 要配合chcp 65001 |
| 给 Windows 记事本编辑的中文文档 | UTF-8带 BOM | 老记事本认 BOM;新版 Win10 已默认 UTF-8 无 BOM |
| 老软件(银行控件、老游戏) | GBK | 它们只认 ANSI 代码页 |
八、总结
- 字符集管编号,编码管存储——乱码 = 用错误的规则解释字节
- GBK 是中文 Windows 的老母语(2 字节/汉字),UTF-8 是全世界通用的新语言(3 字节/汉字)
- 乱码的本质是"解释规则用错了",不是文件坏了
- 现代开发一律 UTF-8 无 BOM;碰到 cmd / 老软件再单独处理
- Windows 上遇到编码问题,优先用 PowerShell 而不是 cmd