约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
文本从写入到展示要过好几道「编码 ↔ 解码」的关:编辑器、文件存储、网络传输、数据库、终端。只要「写入用的编码」和「读取时的假定编码」对不上,就产生乱码。好消息:错位的长相是有规律的——同一种错位永远产生同一种乱码长相,所以看长相就能反推病因。
| 乱码长相 | 病因 | 修法 |
|---|---|---|
| 锟斤拷 / � 反复出现 | UTF-8 解码失败被替换成 U+FFFD 后再次转码,信息已丢 | 从源头重取数据;本地无解 |
| 我是 之类拉丁扩展字符 | UTF-8 字节被按 Latin-1 / Windows-1252 解读 | 按 Latin-1 重编回字节,再按 UTF-8 解 |
| 第一 , 等同款变体 | 同上;页面缺 charset 声明时高发 | 补 <meta charset="utf-8">;历史数据按上法修复 |
| ÎÒÊÇ 之类大写拉丁堆 | GBK / GB2312 字节被按 Latin-1 误读 | 按 Latin-1 还原字节,再按 GBK 解 |
| 清一色问号 ???? | 写入时就丢了:目标编码没有这些字,被换成 ? | 改连接 / 列编码为 utf8mb4;已丢的数据无法恢复 |
| 方框口口(能复制出正常字) | 字体缺字形,数据其实是好的 | 换字体;不用修数据 |
const fixed = new TextDecoder("utf-8")
.decode(new TextEncoder().encode(garbled));
// 原理:把「被误读的字符」按 latin1 编回原始字节,再按 utf-8 正确解码
// Python 等价:garbled.encode("latin-1").decode("utf-8")// Node 无内置 GBK 解码器,用 iconv-lite:
const fixed = iconv.decode(Buffer.from(garbled, "latin1"), "gbk");
// Python 等价:garbled.encode("latin-1").decode("gbk")多数情况不能。锟斤拷意味着数据先被 UTF-8 解码失败替换成了 U+FFFD——替换发生的那一刻,原始字节已被丢弃,信息真的丢了。只能从更上游(源文件、原始接口)重新获取。它能修的是「还没到 U+FFFD 那一步」的数据。
不是对称的。GBK → UTF-8 基本无损(极个别生僻字除外),UTF-8 → GBK 会丢字符(emoji、生僻字变 ?)。迁移老系统时建议一次性转 UTF-8 并存档原始数据。
三层检查:locale(LC_ALL/LANG 是否为 *.UTF-8)、终端模拟器的字符编码设置、文件本身的编码。大多数「本地文件正常、终端乱码」都是 locale 没设成 UTF-8;用 编码修复工具先确认文件编码再对症。
给 CSV 文件开头加 UTF-8 BOM(EF BB BF)——Excel 只靠 BOM 识别编码。用编辑器「另存为 UTF-8 with BOM」,或导出时程序自动补三个字节。注意带 BOM 的文件喂给某些 Linux 工具时首个列名会带 \uFEFF,按需剥离。