既然你的电脑里出现过“锟斤拷”三个字,恭喜你,你已经亲身经历了一场标准的字符乱码事故。这三个字背后站着的,正是 UTF-8、GB2312、GBK 这几个编码名之间说不清道不明的恩怨。乱码这事,说大不大,但它能把一个好好的交付日变成一夜“考古”:网页白屏、SQL 导入报错、Word 打开全是方块、代码里中文注释变成天书。这篇文章我就把 UTF-8、GB2312、GBK 这三兄弟的来龙去脉、乱码产生的机制、以及不同场景下的排查和转码方案一次讲透,适合后端开发、数据清洗、办公文档处理、以及所有被“中文变乱码”折磨过的人收藏。
编码问题有个特点:不懂原理的时候全靠瞎试,懂了原理之后就是纯体力活。乱码不是玄学,它无非是“写入时的编码”和“读取时的编码”对不上,或者中途被某个环节多转了一次。把这层窗户纸捅破,大部分乱码一眼就能判断出方向。下面我按“原理—成因—实操—排查”的顺序来拆,内容尽量贴近真实的踩坑现场。
1. 三种编码的前世今生:从区位码到全球码
1.1 ASCII:一切字符编码的地基
讲中文编码之前,必须先把 ASCII 说清楚。ASCII 用 7 个二进制位表示 128 个字符,包括大小写英文字母、数字、常见符号和控制字符。后来扩展成了 8 位,也就是 Latin-1,能表示西欧语言的字符。ASCII 为什么重要?因为它定义了“英文字母和数字在计算机里长什么样”,后面的编码为了兼容性,几乎都把 ASCII 作为子集保留下来——在 GB2312、GBK、UTF-8 里,一个英文字母仍然占一个字节,值和 ASCII 完全一致。
这个“单字节兼容 ASCII”的设计直接决定了后续编码方案的长相。比如 GBK 的双字节结构里,为了不和 ASCII 冲突,规定双字节的首字节从 0x81 到 0xFE,尾字节从 0x40 到 0xFE(去掉 0x7F)。这样一来,解析器看到一个字节落在 0x81-0xFE 范围,就知道后面还跟着一个字节,如果看到 0x41(字母 A),就知道它是独立的 ASCII 字符。这个设计在当时很聪明,但也埋下了隐患——尾字节 0x40 到 0x7E 这段区间和 ASCII 可见字符是重叠的,一旦流式解析出错,整个序列就全乱了。
1.2 GB2312 与 GBK:中文世界的两代方案
GB2312 是 1980 年发布的中文编码标准,用两个字节表示一个汉字,一共收了 6763 个汉字和 682 个其他符号(包括标点、罗马数字、希腊字母等)。它的设计基于“区位码”的概念:整个字符集是一个 94×94 的矩阵,行是“区”,列是“位”,每个汉字对应一个区号和一个位号。GB2312 覆盖了绝大多数常用汉字,但在实际使用中很快暴露了问题——很多生僻字、繁体字、人名地名用字都没有收录。
GBK 就是为了补 GB2312 的窟窿而出现的,1995 年发布,向下兼容 GB2312,汉字收录量扩展到 21003 个,还包含了藏文、蒙文等少数民族文字以及日文假名、韩文谚字。Windows 系统里常说的代码页 936(cp936)指的就是 GBK,这也是为什么很多旧软件导出的中文文件,在 Linux 上会被识别成 “GBK” 或者 “cp936”。严格来说,GB2312、GBK、GB18030 是递进关系:GB18030 又进一步扩展到了全 Unicode 字符集,用四字节编码表示所有 Unicode 字符。日常处理老系统文件,遇到“拼多多时代的 GB2312 文件”,用 GBK 去解十有八九是对的。
1.3 UTF-8:为什么互联网最终选了它
UTF-8 是 Unicode 的一种变长编码方案。Unicode 给每个字符分配一个唯一的编号(码点),UTF-8 则负责把这个编号转换成 1 到 4 个字节。规则不复杂:ASCII 字符仍然是一个字节(0xxxxxxx),拉丁扩展字符用两个字节(110xxxxx 10xxxxxx),而常用汉字落在 U+0800 到 U+FFFF 区间,用三个字节表示(1110xxxx 10xxxxxx 10xxxxxx),四字节留给 emoji 和一些冷门字符。
以“中”字为例:它的 Unicode 码点是 U+4E2D,在 UTF-8 里编码成E4 B8 AD三个字节;而在 GBK 里是D6 D0两个字节。同一个字,两种方案存出来长度不一样,这是很多人第一次接触编码时最容易困惑的地方。UTF-8 最大的优势是自同步性——解析器从任意位置开始都能正确切分字符;而且纯英文文本的 UTF-8 和 ASCII 完全一致,所以它成了 HTML、JSON、XML 这类互联网协议的默认选择。
那为什么不直接用 UTF-16?因为 UTF-16 虽然对东亚字符友好(绝大多数汉字直接两个字节),但它不兼容 ASCII:英文字母也会变成两个字节,其中还经常夹着一个00(空字节),在 C 语言里这类字符串很容易被截断。所以最终胜出的是 UTF-8,这是个“兼顾兼容性、空间、解析性”的折中方案。需要提醒的是,UTF-8 还有带 BOM 的变体(UTF-8 with BOM),文件开头三个字节是EF BB BF,Windows 下的记事本很喜欢加这个东西,但 Linux 和 macOS 的工具链经常被它惹毛。
2. 乱码是怎么发生的:三类典型事故复盘
2.1 字节流没坏,是“解读姿势”错了
我见过太多人一遇到乱码就想着用什么工具“修复”,其实绝大多数乱码的字节流根本没有损坏,纯粹是读取方用错了解码方式。举个最直观的例子:一段文本按 GBK 编码存成字节D6 D0 CE C4(“中文”两个字),如果某个程序按 UTF-8 去解码,开发者会立刻发现不对劲——D6在 UTF-8 里是非法首字节,解码器就会报错或者输出替换字符;如果按 Latin-1 去解,则会显示成ÖÐÎÄ这种“字母带帽”的乱码。
理解了这一点,乱码排查就变成了“找出这段字节当初是用什么编码写的”。网页乱码、Excel 打开 CSV 乱码、日志文件乱码,大部分属于这一类。解决办法也不是去修改字节,而是让读取方用正确的编码重新读取。比如浏览器里可以手动切换编码,编辑器里可以用“以指定编码重新打开”,Java 里可以new String(bytes, "GBK")。方向对了,乱码立刻消失。
2.2 双重转码:锟斤拷是怎么炼成的
比“解读姿势错”更头疼的是字节流已经被破坏,最经典的产物就是“锟斤拷”。它的形成路径是这样的:一段 UTF-8 编码的中文文本,被某个程序误当成 GBK 解码。GBK 解码过程中遇到无法识别的字节序列时,会把它们替换成 Unicode 的替换字符 U+FFFD(显示为一个带问号的黑色菱形?)。然后这个替换字符再被存成 UTF-8,就会变成EF BF BD三个字节。最后,这段EF BF BD EF BF BD又被某个 GBK 程序读取,就显示成了“锟斤拷”——EF BF对应“锟”,BD EF对应“斤”,后面的组合继续套。你在网上看到的“锟斤拷”刷屏,本质上就是“UTF-8 被 GBK 消费之后产生的疤痕组织”。
这里要特别强调:一旦文本被转换成替换字符,原始信息已经永久丢失,任何转码工具都救不回来。所以当你判断文件已经出现大量?或�时,唯一靠谱的出路是找回原始文件的备份,或者从生成它的系统里用正确编码重新导出。这一点怎么强调都不过分——我见过有人对着一个已经损坏的文件折腾了一下午,最后发现重新导一遍只要五分钟。
2.3 声明与实际不一致:网页和数据库的隐藏雷区
除了文件层面的编码错位,还有一类乱码是因为“声明”和“实际内容”对不上。最常见的就是 HTML 文件里写了<meta charset="utf-8">,但文件本身是用 GBK 编码保存的。浏览器以声明为准,用 UTF-8 去解码一份 GBK 文件,结果自然全是乱码。反过来也一样:声明了 GBK,文件却是 UTF-8 的,也会花屏。这类问题在热词里的出现频率极高,很多人在搜索引擎里贴出来的一整段<!doctype html>代码,问题几乎都是“meta 声明和编辑器右下角的编码不一致”。
数据库场景也类似:MySQL 连接串里写了characterEncoding=UTF-8,但表的默认字符集是latin1,或者服务端character_set_server是 GBK,数据入库时就被转了一道,查询出来再转一道,两头不对付,中文就成乱码了。这条链路上的每个节点都有“字符集声明”,任何一个节点声明错了,后面的全部白搭。数据库乱码排查的精髓是:沿着“客户端连接 → 服务端 → 表结构 → 字段”这条链路,把所有字符集设置打出来对比,而不是盲目 ALTER TABLE。
2.4 字体缺失与渲染层乱码:看着像乱码,其实不是
还有一类“伪乱码”:数据本身完全正常,问题出在渲染层。比如系统里没有安装中文字体,Word 打开后汉字显示成方块(□□□□);再比如老文档指定了“楷体_GB2312”,而 Windows 10/11 默认不包含这个字体,系统会自动替换字体,替换过程中可能因为字重不匹配、字形名映射失败,出现“加粗变形、笔画发虚”的现象。你在实际工作中遇到 “Word 楷体加粗异常”“方正仿宋 GBK 装了没用”,多半不是编码问题,而是字体文件、字体名称、字重匹配的问题。
“伪乱码”还有一个常见变种:URL 里的%E4%B8%AD。很多人看到一串百分号就以为乱码了,其实这是 URL 编码(Percent-encoding),%E4%B8%AD就是“中”字的 UTF-8 字节序列,后端拿到之后需要 URL decode 而不是转码。类似的还有邮箱附件文件名里的=?UTF-8?B?...?=,这是 MIME 编码,正常解密即可,千万别拿去转 GBK。
3. 实战方案:多种场景下的正确转码姿势
3.1 命令行方案:file 识别 + iconv 转换的组合拳
在 Linux 或 macOS 下,处理文件乱码我推荐两步走:先识别,再转换。识别用file命令,注意要加-i参数输出 MIME 类型和字符集:
file -i 乱码文件.txt # 输出示例:text/plain; charset=iso-8859-1如果输出charset=iso-8859-1,说明file没能认出这是中文编码,这时可以再用file -i的变体或者直接根据来源判断,一般 Windows 导出、旧软件生成的文件优先按 GBK 处理。
确认编码后,转换用iconv:
iconv -f GBK -t UTF-8 乱码文件.txt > 新文件.txticonv支持-c参数跳过非法字符,但我要泼一盆冷水:不要轻易用-c。它会把解不开的字符静默丢弃,等于主动丢数据。转换失败报 “Illegal byte sequence” 时,先怀疑编码判断错了,而不是急着忽略错误。批量转换可以用一个简单的循环:
for f in *.txt; do iconv -f GBK -t UTF-8 "$f" > "utf8_$f" && mv "utf8_$f" "$f" done批量操作之前务必先拿一个文件验证,并且保留原文件备份。我这个习惯是从一次事故里学来的——当时一个脚本处理了三百多个 CSV,跑到快结束才发现开头几个文件不是 GBK,而是已经在 UTF-8 了,直接二次转码,全部乱掉。
3.2 Python 脚本:GBK 转 UTF-8 的正规姿势
写 Python 处理编码,核心是 3.0 时代之后的“文本模型”:打开文件时必须明确指定encoding,字符串在内存里永远是 Unicode,读写文件时才做编解码。读 GBK 文件再存成 UTF-8,最基本的三行:
with open('old.csv', 'r', encoding='gbk', errors='replace') as src: text = src.read() with open('new.csv', 'w', encoding='utf-8') as dst: dst.write(text)errors='replace'的作用是遇到非法字节序列时不抛出异常,而是替换成�。这在前期试探时很有用,但正式处理时我更推荐先不设这个参数,让程序报错,这样能第一时间发现哪些文件的编码判断可能不对。
批量转换并自动判断编码时,可以配合chardet或charset_normalizer库做探测。但注意,探测结果只是“猜测”,尤其对纯中文短文本经常误判。我惯用的策略是:先按 GBK 解码,遇到UnicodeDecodeError再回退到 UTF-8,而不是完全依赖探测库:
from pathlib import Path def read_text_auto(path: Path): raw = path.read_bytes() for enc in ('gbk', 'utf-8'): try: return raw.decode(enc), enc except UnicodeDecodeError: continue return raw.decode('utf-8', errors='replace'), 'unknown' for p in Path('.').glob('*.csv'): text, enc = read_text_auto(p) print(f'{p.name}: {enc}') p.write_text(text, encoding='utf-8')这里有个被人反复踩的坑:不要用latin-1做“万能解码中间层”。latin-1能把任意字节都映射成字符,不会报错,看起来很“普适”,但它会把字节 0x81、0xFE 这些转成奇怪的控制字符,后续再 encode 回去时多绕了一道,容易造成不可逆变化。遇到不确定的编码,宁可报错也别用 latin-1 打太极。
3.3 编辑器与 IDE:IDEA、VS Code、Notepad++ 的编码三件套
开发环境里的乱码,九成是“文件编码设置”和“运行环境编码设置”分开管导致的。以 IntelliJ IDEA 为例,设置项在Settings > Editor > File Encodings,里面有几个独立的值:Global Encoding、Project Encoding、Properties Files。三者的关系是:Project Encoding 决定当前项目的文件读写编码,Global Encoding 是新建项目的默认值,Properties Files 单独管.properties文件。改的时候要三个一起看,不要只改一个。
改完 IDEA 界面里的文件编码,如果控制台输出还是乱码,问题往往出在 JVM 的运行参数上。控制台输出的乱码,本质是System.out写入控制台时的字节编码和 IDE 控制台解码时用的编码不一致。排查时先看启动日志,如果出现类似Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=GBK的提示,说明环境变量JAVA_TOOL_OPTIONS里被写死了一个编码参数,所有 JVM 进程都会读到它。解决方法是清掉这个环境变量,或者在启动脚本里显式覆盖成 UTF-8:
# Linux / macOS unset JAVA_TOOL_OPTIONS # Windows PowerShell Remove-Item Env:JAVA_TOOL_OPTIONSVS Code 的操作更直觉化:打开一个乱码文件后,点击右下角的编码按钮(比如“UTF-8”或“GBK”),选“Reopen with Encoding”,尝试常用编码直到文本恢复正常。确定后如果需要把文件长久转成 UTF-8,再选“Save with Encoding”。这个“打开时重新解读”和“保存时转码”是两件事,很多初学者点错成“Save with Encoding”,等于把 GBK 字节重新存了一遍,文件反而二次损坏。
Notepad++ 的情况同理,“转为 UTF-8 编码”和“以 UTF-8 编码”是两个完全不同的菜单项。前者表示“把当前按正确编码解析出来的内容,重新转存为 UTF-8”,后者只是换个解码方式看同一份字节。你要转码就用前者,看乱码原因用后者来回切换对比。
3.4 专业工具链:MATLAB、LabVIEW、ABAP 的编码处理
MATLAB 的乱码问题比较特殊。新版 MATLAB 在 Windows 上默认使用 UTF-8 保存.m文件,但旧版本(以及新版读取旧文件时)常遇到 GBK 编码的.m文件。如果你打开后发现中文注释变成乱码,可以在 MATLAB 命令行尝试切换默认字符集:
feature('DefaultCharacterSet', 'UTF-8')注意,feature是非官方接口,不同版本行为不完全一致。更稳妥的方式是用编辑器打开文件后,右键/菜单里找到“另存为”,保存时选择 UTF-8 编码,并确认原有内容已经被正确解析。如果你用的是 MATLAB Coder 做代码生成,还要单独检查代码生成配置里的编码选项——.m文件的编码和生成的 C 代码编码是两套开关,很多人在.m文件里改了半天,生成的代码依然乱码,就是因为没有去配置界面搜 “Encoding” 关键词。另外,Simulink 模型文件如果乱码,可以查看slCharacterEncoding的相关设置,但这个函数需要在有 Simulink 的环境里才能执行,日常排查看文档验证即可。
LabVIEW 里 GBK 和 Unicode 的转换,本质上是“LabVIEW 字符串本质上是字节数组”这件事的延伸。LabVIEW 内部从 8.0 开始用 UTF-8 作为字符串的默认编码,但调用 Windows API 或读取旧系统文件时,拿到的往往是 GBK 字节。最简单的方案是不写复杂节点,借助 System Exec.vi 调用系统命令:
Get-Content -Path input.txt -Encoding Default | Set-Content -Path output.txt -Encoding UTF8PowerShell 5.1 里的-Encoding Default指的就是系统 ANSI 代码页,中文系统上就是 GBK。如果你习惯在 LabVIEW 内部处理,标准做法是调用 .NET 节点:创建System.Text.Encoding.GetEncoding(936)对象,用GetString(byte[])把 GBK 字节数组转成字符串,再用GetBytes(string)把字符串按 UTF-8 编码输出。代码层面就是这三步,剩下的都是控件连线。
ABAP 环境里的转码,思路一致但语法不同。SAP 提供了编码转换类CL_ABAP_CONV_IN_CE和CL_ABAP_CONV_OUT_CE,用于在内部文本和指定编码的字节流之间互转。也可以直接用IN CHARACTER ENCODING关键字控制字符串与字节流的转换。核心是:把文本先变成字节数组(XSTRING 或 X 序列),这时指定目标编码;把字节数组变成文本时,指定来源编码。在整套 SAP 技术栈里,系统代码页、数据库代码页和 Unicode 标志(SAP 系统是否启用 Unicode)会叠加重重影响,遇到问题先查CCM事务码或者系统代码页,别急着改程序。
3.5 办公文档与中文字体:Word 里的 GB2312 字体问题
文档场景的“乱码”和程序员说的乱码不太一样,但热词里关于“楷体_GB2312”“仿宋_GB2312”以及“方正小标宋”的搜索量常年不低,说明这类字体问题在正式文档制作中相当普遍。
先说规律:Windows 10/11 自带的字体里通常没有“楷体_GB2312”“仿宋_GB2312”这些老命名,只有“楷体”“仿宋”。如果你收到一份文档,里面指定了 GB2312 字体,但本机没装,Word 会用“字体替换”机制,把不存在的字体映射到别的中文字体。字体替换后文本本身没有变化,肉眼看到的“加粗异常、字形不对、字符缺棱缺角”是字体的替代渲染造成的。
如果系统里已经装了“楷体_GB2312”,但 Word 里加粗之后笔画发虚、显示异常,这多半是因为 GB2312 老字体只有 Regular 字重,没有真正的 Bold。Word 会对它做“伪加粗”(faux bold),也就是把字形边缘加粗、变形,小字号下尤其难看。解决方案有三个方向:一是避免加粗,改用字号、字色、下划线等做强调;二是换成有完整字重的字体(比如“楷体”或者开源字体“思源宋体”);三是检查 Word 的“高级”字体设置,看是否选中了“为字体嵌入设置替代字体”之类的选项。在正式交付前,最好用目标机器验证一遍渲染效果。
Mac 上 Word 使用“方正仿宋 GBK”这类字体的难点在于,Windows 下安装字体简单,Mac 需要手动安装到“字体册”,并且安装后 Word 里看到的字体名可能是英文编码(例如FZXBSK--GBK1-0),看起来像乱码,其实是正常现象。另一个经验是:跨平台传 Word 文档,尤其是包含 GB2312 字体的正式文件,建议保存时勾选“将字体嵌入文件”,这样换机器后字体不会丢,但文件体积会增大不少,而且部分有版权的字体不允许嵌入,具体看“字体许可证”里是否允许“嵌入”权限。如果你只是需要一个能交差的文件,不追求完美还原,直接在 Mac 上把字体替换成“宋体-简”或“楷体-简”交付,往往更省事。
4. 乱码排查四步法与常见问题速查表
4.1 快速定位:看、猜、查、转四步法
我处理乱码问题有一套固定流程,这里分享给大家,简单说就是“看、猜、查、转”。
第一步“看”,就是观察乱码的外貌。乱码形态会透露大量信息:如果是“锟斤拷”这种固定组合,基本能断定是 U+FFFD 被二次消费;如果是一串ä¸Â,八成是 UTF-8 字节被 Latin-1 解读;如果是清一色的?,多半是编码转换时遇到了无法表示的字符;如果是方块,往往是字体缺失,不是编码问题。
第二步“猜”,猜测编码来源。问自己三个问题:这份文件是从哪个系统导出的?导出时有没有经过中间环节(比如 FTP、邮件、网盘)?打开它的软件默认编码是什么?来源判断比工具检测更可靠,因为工具对短文本的检测经常翻车。
第三步“查”,用工具验证。file -i、Notepad++/VS Code 切换编码预览、Python 尝试多种编码,都可以做验证。注意验证要在副本上进行,不要直接在原始文件上反复保存,否则一个手滑就不可逆了。
第四步“转”,确认编码后做转码。务必备份原始文件,转完一定要抽查验证——找一个中文特征明显的片段,确认转换后字完全正确,再大规模处理。
4.2 常见乱码速查表
下面这个表我整理了很久,基本覆盖了日常能碰到的八成场景,遇到问题可以直接对照。
| 看到的形态 | 根本原因 | 处理方向 |
|---|---|---|
| 锟斤拷 | UTF-8 中无法解码的字节被替换为 U+FFFD,又被 GBK 显示 | 字节已损坏,找回原始文件重新导出 |
| 烫烫烫 / 屯屯屯 | 未初始化内存(0xCC / 0xCD)被当作 GBK 字符显示 | 程序内存问题,不是编码转换能解决的 |
| 中文变成 ÖÐÎÄ 之类带符号字符 | GBK 字节被 Latin-1 或类似编码解读 | 用 GBK 重新打开文件 |
| 中文变成 丠之类 | UTF-8 字节被 Latin-1 解读 | 用 UTF-8 重新打开文件 |
| 全部显示为 ?? | 字符在目标编码中不存在,转换时丢失 | 检查是否有中间编码环节,避免多次转码 |
| 方块 □□□ | 字体缺失或渲染层问题 | 安装对应字体,或修改字体映射 |
| %E4%B8%AD | URL 百分号编码 | 用 decodeURIComponent 解码,不是转码 |
| 头部出现 BOM 乱码(如 ) | UTF-8 with BOM 被当 GBK/Latin-1 阅读 | 设置编辑器按 UTF-8 打开,或移除 BOM |
看到没,“锟斤拷”和“烫烫烫”其实都不是能通过转码修复的,它们一个代表数据已经坏死,一个代表程序有 bug。真正能救回来的乱码,绝大多数集中在表格第 3 到第 5 行——那只是“解码姿势”不对。
4.3 系统代码页与区域设置:乱码的最后一道隐藏关卡
最后一个很隐蔽的乱码来源是“系统代码页”。Windows 的命令行工具、老软件、Java 老版本程序,都默认使用系统 ANSI 代码页来解析文本,中文系统就是 GBK(代码页 936)。你可以在命令行输入:
chcp看到的输出就是当前活动代码页。936代表 GBK,65001代表 UTF-8。有些程序的乱码问题,纯粹是因为它被要求在代码页 936 环境下工作,而文件却按 UTF-8 读——或者反过来。临时切换代码页可以用:
chcp 65001但要注意,65001 模式下老程序可能有兼容性问题,尤其是那些依赖 ANSI 字符串边界判断的中文软件,会莫名报错。
macOS 和 Linux 上的对应物是locale环境变量。locale命令查看当前语言环境,LANG=zh_CN.UTF-8表示 UTF-8,LANG=zh_CN.GBK表示 GBK。在 shell 里启动某个乱码程序前,先跑一遍locale看看环境变量的值,很多时候只是某个脚本在启动时 export 了一个不合适的 LANG:
export LANG=zh_CN.UTF-8这里回到热词里的 IDEA 场景:Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=gbk之所以让人困惑,是因为它让整个 JVM 进程以 GBK 作为默认文件编码运行,无论是读配置文件、控制台输出还是文件读写,都会受影响。这种行为是环境变量层面的“系统全局设置”,跟 IDEA 的 File Encodings 面板是两套东西。所以排查 Java 相关乱码时,一定要区分“进程内设置”“JVM 启动参数”“系统代码页”三个层面,逐层检查。
4.4 团队规范:从根源上防止乱码
处理了这么多乱码之后,我的感受是:乱码问题最好的解法,是在它发生之前就把它挡住。个人项目无所谓,团队协作时如果编码不统一,简直就是给未来的自己埋雷。我建议至少做这三件事:
第一,新项目统一使用 UTF-8 编码,并在.editorconfig里显式声明:
[*] charset = utf-8 end_of_line = lfEditorConfig能强制主流编辑器遵守编码规则,比口头打招呼有效得多。第二,数据库连接串、HTTP 响应头、HTML 的 meta 声明全部统一写 UTF-8,而且要做到“声明与实际一致”——这是老生常谈,但每次乱码事故几乎都是某处声明和实际不一致。第三,历史遗留的 GBK 文件在进入新系统前,用脚本统一转码并加入验证环节,不要让人手工一个个打开另存,人一定会漏。
最后再分享一个小技巧:如果你经常处理跨系统文本文件,可以在自己的工位准备一个“三板斧”脚本,输入一个文件,输出“原编码猜测 + 转成 UTF-8 的结果 + 转换前后字节数对比”。我每次拿到一个不明来源的文件,第一件事就是跑这个脚本,确认编码后再做后续操作。这套流程跑熟了之后,遇到乱码你就不会慌,因为你知道:字节不会说谎,只是读它的人用错了姿势。