3786 字
10 分钟
字符编码入门:从 VS Code 终端乱码到 Unicode
2026-07-06

字符编码问题看起来很玄学,实际通常只是同一串字节被不同的编码规则解释了。本文先解决最常见的乱码场景,再理解背后的编码知识。

1. 先看一个最常见的问题:VS Code 终端乱码#

假设程序本来应该输出:

你好,世界!

终端里却出现 浣犲ソ、问号、方框或其他无法辨认的字符。此时不要反复切换编码碰运气,应先区分下面四个环节:

  1. 源文件编码:代码文件以 UTF-8、GBK 还是其他编码保存。
  2. 程序使用的编码:编译器或运行时如何解释源文件,以及程序输出了哪些字节。
  3. 终端使用的编码:终端按照什么规则把字节还原成字符。
  4. 终端字体:字体中是否包含这些字符的字形。

只有前后三个环节采用兼容的编码,文字才能正确显示。编码正确但字体缺字时,通常会显示方框,而不是 浣犲ソ 这类乱码。

1.1 检查 VS Code 文件编码#

VS Code 窗口右下角会显示当前文件编码,例如 UTF-8。单击它可以选择:

  • Reopen with Encoding(通过编码重新打开):不修改文件,只换一种编码解释现有字节。适合找回已经存在的中文内容。
  • Save with Encoding(通过编码保存):按照新编码重新写入文件。确认内容显示正常后,再用它转换编码。

不要在内容仍是乱码时直接“以 UTF-8 保存”,否则可能把错误解码后的文字再次写入文件,使恢复更加困难。

新项目建议统一使用 UTF-8。可以在 VS Code 的 settings.json 中设置:

{
"files.encoding": "utf8",
"files.autoGuessEncoding": true
}

files.autoGuessEncoding 只能进行推测,并不能保证准确。项目已经明确编码时,应以项目约定为准。

1.2 检查 Windows 终端代码页#

在 VS Code 的 PowerShell 或 CMD 终端中运行:

Terminal window
chcp

常见结果如下:

代码页常见含义
65001UTF-8
936简体中文 GBK
437英文 OEM 代码页

如果程序输出 UTF-8,而当前代码页不是 UTF-8,可以在当前终端会话中执行:

Terminal window
chcp 65001

这只改变当前终端会话,不会修改源文件,也不能修复已经损坏的文本。如果项目中的旧程序固定输出 GBK,则把终端统一改成 UTF-8 反而可能产生乱码,需要让程序和终端使用一致的编码。

1.3 Python 输出或文件读取乱码#

先检查 Python 正在使用的标准输入输出编码:

import sys
print(sys.stdin.encoding)
print(sys.stdout.encoding)
print(sys.stderr.encoding)

可以临时使用 UTF-8 模式运行程序:

Terminal window
python -X utf8 main.py

读写文本文件时应明确指定编码,不要依赖不同操作系统可能不同的默认值:

from pathlib import Path
path = Path("message.txt")
path.write_text("你好,世界!", encoding="utf-8")
text = path.read_text(encoding="utf-8")
print(text)

如果读取旧的 GBK 文件,应使用实际编码:

with open("legacy.txt", "r", encoding="gbk") as file:
text = file.read()

不要为了“消除报错”就随意添加 errors="ignore"。它会静默丢弃无法解码的数据。排查阶段使用默认的 errors="strict" 更容易发现编码不匹配。

1.4 C 和 C++ 中文输出乱码#

C/C++ 需要同时考虑源文件编码、编译器执行字符集和终端编码。MSVC 可以使用 /utf-8,让源字符集和执行字符集采用 UTF-8:

Terminal window
cl /utf-8 main.cpp

GCC 和 Clang 通常把 UTF-8 作为源文件编码;需要明确指定时可以使用:

Terminal window
g++ -finput-charset=UTF-8 -fexec-charset=UTF-8 main.cpp -o main.exe

同时确认终端代码页为 65001。在 Windows 上,涉及系统 API 的正式程序还应优先使用 Unicode 版本的 API 和宽字符接口,而不是依赖当前系统代码页。

1.5 字符显示成方框#

如果英文正常、中文或 emoji 显示成空白方框,编码可能没有问题,而是字体缺少对应字形。可以在 VS Code 中选择覆盖范围更完整的等宽字体:

{
"terminal.integrated.fontFamily": "Cascadia Mono, Microsoft YaHei UI"
}

字体名称需要与本机已安装字体一致。emoji 的显示还会受到终端渲染能力和操作系统版本影响。

1.6 “输出”面板正常,但终端乱码,或情况相反#

VS Code 的“终端”和“输出”是两个不同区域。调试器、构建任务以及 Code Runner 等扩展可能把内容写到“输出”面板,也可能启动独立进程,它们不一定继承集成终端中的 chcp 设置。

排查时先确认内容实际出现在哪里:

  • 在“终端”中直接运行程序,观察是否仍然乱码。
  • 查看 .vscode/tasks.json 中实际执行的命令和所用 shell。
  • 查看相关扩展是否提供“在终端中运行”的选项。
  • 如果只有某个扩展乱码,优先检查该扩展的编码或运行方式,而不是修改整个项目的文件编码。

例如,使用 Code Runner 时可以让代码在集成终端中执行:

{
"code-runner.runInTerminal": true
}

修改设置后应新建一个终端,因为已经运行的终端进程不会自动获得所有新配置。

2. 字符编码究竟是什么#

理解编码只需要分清三个概念:

概念含义示例
字符(character)人所理解的文字或符号A😀
码点(code point)Unicode 为字符分配的编号U+4E2D
字节(byte)计算机实际存储和传输的数据单位UTF-8 中 E4 B8 AD

编码负责把字符或码点转换成字节,解码负责把字节还原成字符:

字符 --编码--> 字节
字符 <--解码-- 字节

以 Python 为例:

text = "中文"
data = text.encode("utf-8")
print(data) # b'\xe4\xb8\xad\xe6\x96\x87'
print(data.hex(" ")) # e4 b8 ad e6 96 87
print(data.decode("utf-8")) # 中文

程序中的字符串和磁盘中的字节不是一回事。在 Python 3 中,str 表示 Unicode 文本,bytes 表示原始字节。

3. 为什么会出现乱码#

乱码最典型的原因是:写入时使用编码 A,读取时却使用编码 B。

text = "你好"
utf8_data = text.encode("utf-8")
# 使用错误的编码解码,可能报错或得到乱码
wrong_text = utf8_data.decode("gbk", errors="replace")
print(wrong_text)

常见现象及原因:

现象常见原因
出现 浣犲ソ 一类文字UTF-8 字节被当作 GBK 等编码解码
出现 é中UTF-8 字节被当作 Latin-1 或 Windows-1252 解码
出现 解码失败后使用了替换字符 U+FFFD
出现 ?编码时目标字符集无法表示该字符,被替换为问号
出现方框或空白字体缺少字形,或渲染器不支持
出现 UnicodeDecodeError使用了错误编码,或文件字节已损坏

一旦原始字节被丢弃、替换成 ?,信息可能已经不可逆地丢失。处理乱码文件前,最好先备份原文件。

4. 常见字符集与编码#

4.1 ASCII#

ASCII 使用 7 位表示 128 个字符,包括英文字母、数字、标点和控制字符。例如:

A -> 65 -> 0x41
0 -> 48 -> 0x30

ASCII 不能表示中文。UTF-8 的前 128 个字符与 ASCII 完全兼容,因此纯 ASCII 文本也是合法的 UTF-8 文本。

4.2 ISO-8859-1 与 Windows-1252#

ISO-8859-1 又称 Latin-1,使用一个字节表示西欧字符。Windows-1252 与它相近,但在 0x800x9F 范围定义了不同字符。

它们都不能完整表示中文。由于 Latin-1 能把任意字节映射到字符,有时会被程序用于“无报错地读取字节”,但这并不代表文本被正确解码。

4.3 Unicode#

Unicode 是统一字符标准,为世界各地的字符分配码点。它解决的是“每个字符对应哪个编号”,不是“这些编号如何存成字节”。

UTF-8、UTF-16 和 UTF-32 才是 Unicode 的具体编码方式。同一个字符的码点相同,但在不同 UTF 编码中的字节表示不同。

4.4 UTF-8#

UTF-8 是目前最常用的 Unicode 编码,具有以下特点:

  • 使用 1 到 4 个字节表示一个码点。
  • ASCII 字符只占 1 个字节,与 ASCII 兼容。
  • 常用汉字通常占 3 个字节。
  • 不依赖字节序,适合网页、配置文件、源代码和网络传输。
for char in ["A", "中", "😀"]:
data = char.encode("utf-8")
print(char, data.hex(" "), len(data))
# A 41 1
# 中 e4 b8 ad 3
# 😀 f0 9f 98 80 4

没有历史兼容要求时,文本文件通常优先选择 UTF-8(无 BOM)

4.5 UTF-16 与 UTF-32#

UTF-16 通常使用 2 个或 4 个字节表示一个码点,UTF-32 固定使用 4 个字节。多字节数值需要区分字节序:

  • UTF-16 LE:低位字节在前。
  • UTF-16 BE:高位字节在前。
  • UTF-32 同样有 LE 和 BE。

UTF-16 常见于 Windows API、Java 和一些旧式文本格式;UTF-32 便于按固定宽度处理码点,但空间开销较大。注意:即使使用 UTF-16,一个用户眼中的字符也不一定恰好等于一个 16 位代码单元。

4.6 GB2312、GBK 与 GB18030#

这三种编码主要用于中文环境,关系可以粗略理解为逐步扩展:

编码特点建议用途
GB2312较早的简体中文字符集,覆盖范围有限仅用于兼容旧数据
GBK扩展 GB2312,包含更多汉字和符号兼容旧版中文 Windows 程序
GB18030中国国家标准,覆盖范围更广,可表示 Unicode 字符有明确国标或旧系统要求时使用

新项目通常仍应优先选择 UTF-8;只有对接旧文件、旧数据库或特定系统时,才使用它们实际要求的中文编码。

4.7 一张对比表#

编码单个字符占用中文支持ASCII 兼容常见场景
ASCII1 字节早期英文文本、协议基础字符
Latin-11 字节西欧旧系统
UTF-81~4 字节Web、Linux、源代码、跨平台文本
UTF-162 或 4 字节Windows API、部分运行时内部表示
UTF-324 字节特定内部处理场景
GBK1 或 2 字节旧版中文 Windows 软件
GB180301、2 或 4 字节国标要求、中文旧系统兼容

5. BOM 是什么#

BOM(Byte Order Mark,字节顺序标记)是文件开头的一段特殊字节,可用于标识 Unicode 编码和字节序。

编码常见 BOM 十六进制
UTF-8EF BB BF
UTF-16 LEFF FE
UTF-16 BEFE FF
UTF-32 LEFF FE 00 00
UTF-32 BE00 00 FE FF

UTF-8 不需要依靠 BOM 判断字节序。多数源代码和配置文件推荐使用 UTF-8 无 BOM,但部分 Windows 软件会用 BOM 识别 UTF-8。

Python 读取带 BOM 的 UTF-8 文件时,可以使用 utf-8-sig 自动移除 BOM:

with open("data.txt", "r", encoding="utf-8-sig") as file:
text = file.read()

6. 其他高频场景#

6.1 CSV 在 Excel 中打开乱码#

某些版本或配置下的 Excel 不能自动识别无 BOM 的 UTF-8 CSV。可以按使用对象选择方案:

  • 仅供程序读取:优先使用标准 UTF-8。
  • 主要供 Windows Excel 双击打开:可尝试 UTF-8 with BOM,即 Python 中的 utf-8-sig
  • 对方系统明确要求 GBK:按接口约定导出 GBK,并确认字符不会丢失。
import csv
with open("users.csv", "w", encoding="utf-8-sig", newline="") as file:
writer = csv.writer(file)
writer.writerow(["姓名", "城市"])
writer.writerow(["小明", "上海"])

6.2 网页中文乱码#

HTML 页面应尽早声明 UTF-8:

<meta charset="UTF-8">

HTTP 响应也应提供正确的 Content-Type

Content-Type: text/html; charset=utf-8

HTTP 头的优先级通常高于 HTML 内的声明。页面文件、服务端响应头和数据库连接应采用一致或可正确转换的编码。

6.3 数据库出现乱码或 emoji 写入失败#

数据库需要同时检查:

  1. 数据库、表和字段使用的字符集。
  2. 客户端与数据库连接使用的字符集。
  3. 驱动程序发送和接收字符串的方式。

MySQL 中应优先使用真正覆盖完整 Unicode 的 utf8mb4。历史上的 utf8 别名可能只支持最多 3 字节的 UTF-8,无法存储部分 emoji 和扩展字符。

6.4 Git 显示中文文件名异常#

文件内容编码、文件名的显示方式和换行符是三个不同问题。Git 中可按需检查:

Terminal window
git config --get core.quotepath
git config --get core.autocrlf

core.quotepath=false 可以让 Git 更直接地显示非 ASCII 文件名;core.autocrlf 管理的是换行符转换,并不负责字符编码。

7. 如何判断文件编码#

没有 BOM 或外部协议时,仅凭字节通常无法百分之百确定编码。可靠性从高到低一般是:

  1. 文件格式、HTTP 头或接口文档明确声明。
  2. BOM 或格式自身的固定标记。
  3. 项目约定和生成文件的软件设置。
  4. 编辑器或检测工具根据字节分布猜测。

Linux、macOS 或 Git Bash 中可以使用 file 辅助判断:

file --mime-encoding example.txt

Python 可以先读取原始字节,再根据已知来源尝试解码:

from pathlib import Path
data = Path("example.txt").read_bytes()
for encoding in ("utf-8-sig", "utf-8", "gb18030"):
try:
text = data.decode(encoding)
print(f"可以按 {encoding} 解码:{text[:30]!r}")
except UnicodeDecodeError:
print(f"不是有效的 {encoding}")

“能够解码”不一定代表编码正确,因为某些编码可以接受非常多的字节组合。最终仍需结合文件来源和内容判断。

8. 通用乱码排查流程#

遇到乱码时,可以按下面的顺序检查:

  1. 保留原始数据:先复制文件或保存原始响应字节,避免二次保存造成数据丢失。
  2. 确认乱码出现的位置:编辑器、终端、日志、网页、数据库还是导出的文件。
  3. 确认数据源编码:查看协议、响应头、文件设置、BOM 或生成端配置。
  4. 确认解码端编码:代码中的 encoding、终端代码页、数据库连接字符集是否一致。
  5. 查看原始字节:不要只盯着已经乱码的文本,可用十六进制查看器或 bytes.hex() 检查。
  6. 检查字体:编码一致但显示方框时,换用包含相应字形的字体。
  7. 只转换一次:正确解码为 Unicode 字符串后,再按目标编码重新写入。

9. 实用建议#

  • 新项目默认使用 UTF-8,源代码和配置文件通常使用 UTF-8 无 BOM。
  • 在文件读写、网络协议和数据库连接的边界明确指定编码。
  • 内部处理尽量使用 Unicode 字符串,只在输入输出边界进行编码和解码。
  • 不要混淆字符编码、字体、换行符、Base64 和 URL 百分号编码。
  • 不要用 errors="ignore" 掩盖问题;它可能悄悄删除数据。
  • 对接旧系统前先确认其真实编码,不要仅凭“中文系统”猜测为 GBK。
  • 转换重要文件前保留原始副本,并抽查中文、特殊符号和 emoji。

字符编码并不神秘:始终追踪“当前拿到的是字符还是字节、字节由什么编码产生、接收端用什么编码解释”,大多数乱码问题就能沿着这条链路定位。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

字符编码入门:从 VS Code 终端乱码到 Unicode
https://minikou.cloud/posts/character_encoding/
作者
minikou
发布于
2026-07-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录