01 / ASCII 不够用,但兼容不能推倒重来
早期文本交换需要统一字母、数字和控制符,7 位 ASCII 为英语环境提供了共同基础。其他语言需要更多字符,各地区在兼容和容量约束下发展自己的编码。同一字节值因约定不同可能代表不同文字,跨地区文件交换因此不能只传字节,还必须知道解释规则。
GB2312 满足较早的简体中文交换需求,GBK 扩大了汉字覆盖范围;GB18030 又以一、二、四字节结构扩展表示能力。它们不是“中文就是两个字节”的同义词。GBK 不能表达所有 Unicode 字符,生僻字和 emoji 会暴露兼容边界。转码时静默输出问号,文件看似成功,原字符却已经丢失。
Latin-1 和 Windows-1252 也不能混为一谈:0x80–0x9F 在前者对应控制字符,在后者部分对应可见标点。浏览器对一些历史编码标签有兼容别名,因此同一个“Latin-1”标签在不同 API 中不一定具有相同含义。本页 Latin-1 明确指 ISO-8859-1。
02 / Unicode 统一编号,UTF 负责存储
Unicode 的目标是让各语言字符共享编码空间,减少区域字符集之间的割裂。码点是编号,UTF-8、UTF-16 则是把编号变成代码单元和字节的方式。因此“文件是 Unicode”还不够,读取方仍需要知道具体编码、字节序和边界。
UTF-8 保持 ASCII 字节不变,以一至四字节表达 Unicode 标量值,这让既有 ASCII 协议和字节流容易兼容。UTF-16 使用一个或两个 16 位代码单元;补充平面字符需要代理对,落到文件还需确定大端或小端。孤立代理项不是可独立编码的 Unicode 标量值,本页拒绝把它悄悄变成替代字符。
JavaScript 的 length 计 UTF-16 代码单元,字符串迭代按码点;用户看到的一个字符还可能由多个码点构成。例如 e 加组合重音、家庭 emoji 都可能是一个扩展字素簇。字素簇也不是字体排版的字形数。本页分别显示四种计数,不用一个“长度”掩盖不同需求。
03 / 乱码:解释错了,或信息已经丢了
文件写入用 UTF-8,读取却使用另一编码;数据库连接、HTTP 响应头和程序内部约定又可能再次不一致。显示出来的文字随后被重新编码,错误就会固化到下一份文件。乱码通常不是“换个字体”,而是需要查清原字节经过了哪些编码与解码。
如果原始字节仍在,可以尝试按正确编码重新解释;若错误解码已经把无法识别的序列变成 U+FFFD(�),或错误编码把字符变成 ?,多个原值可能合并成一个替代值,无法唯一恢复。文字本来包含 � 或 ? 也是合法情形,不能凭这两个字符就断言文件已经损坏。
无效 UTF-8、截断的多字节字符可以严格检测;但纯 ASCII 同时兼容很多编码,某些乱码也完全能通过另一编码的语法检查。因此“解码成功”不等于“识别正确”。本页保留文件字节,提供明确选择和对照,不给虚假的置信度,不自动修复或规范化文字。
BOM、转码与协议:正确性必须落在边界上
BOM 原本帮助识别 Unicode 字节顺序:UTF-16LE 常以 FF FE 开头,UTF-16BE 为 FE FF。UTF-8 没有字节序问题,不要求 BOM,但 EF BB BF 有时被软件当作编码签名。协议和工具对此有不同约束,例如网络 JSON 的发送方不应添加 BOM。文件头线索不能取代明确的协议约定。
匹配的文件头可以作为标记消费,或者保留成 U+FEFF 内容;目标 BOM 则单独选择。UTF-32 标记也会被报告,但本页不支持 UTF-32 转码。正文里的 U+FEFF 不会被批量删除;换行、组合字符和空白也不自动改变。所谓“清理”可能影响哈希、签名或程序行为,不应藏在转码步骤里。
本页 Unicode 解码使用严格校验,旧编码使用字节往返检查;后者是保守策略,合法但存在非唯一映射的字节也可能被拒绝。目标编码必须能还原同一文字,否则阻止导出。GB18030 采用库提供的映射,不作为 GB18030-2022 标准符合性检测工具。
字符编码把文字变成字节;Hex 和 Base64 把已有字节变成便于检查或传输的文本。把 UTF-8 文本做 Base64 不会自动解决源编码不一致,也不提供加密。字节容器的规范和校验请查看编码与表示。