对所有的乱码 Say Goodbye ——浅谈字符编码
Grammar_hbw · · 科技·工程
还在为乱码而焦头烂额吗?这里有解决所有乱码的方法。(前提是你的文件本身是有意义的文本且不是锟斤拷,cat /dev/urandom产生的乱码谁都没有办法)
常见的编码方式
- ASCII。这是最早的编码方式,只有英文字母、数字、一些符号和控制字符。编码范围 0 ~ 0x7F。
- ISO-8859。欧洲国家有一些带帽子的字母,是 ASCII 所不包含的,他们把 ASCII 没有利用的最高位利用了起来。ISO-8859 也包含了很多种编码,因为各个国家的帽子字母不一样(所以会造成乱码)。ISO-8859 兼容 ASCII 。
- GB2312/GBK/GB18030,我们知道汉字众多,所以一个字节对我们是不够用的,所以这是一种多字节编码。“一个汉字占两个字节”就是 GB2312 时代的说法。GB2312 包含了 6763 个汉字,远远不够,所以微软在 GB2312 的基础上增加了更多汉字,称为 GBK,但 GB2312 和 GBK 的多字节部分都是双字节的。GB18030 增加了四字节的编码,以此包含了 Unicode 的所有码位。GB18030、GBK、GB2312、ASCII 依次兼容。
- Unicode。这个是一个字符集,不是编码(记事本的 Unicode 实际是 UTF-16 LE),包含了世界所有语言的字符。它还在扩展,所以可能暂时还有字符没有包含。
- UTF-8/UTF-16/UTF-32。UTF 的全名是 Unicode 传输格式(Unicode Transformation Format),是用来实现 Unicode 的编码。UTF-16 和 UTF-32 不兼容 ASCII,所以请不要在代码、数据文件中使用,否则会有奇怪的问题。 :::info[UTF-8 编码规则]
见下表,将 Unicode 编号的二进制值从右往左、从低位到高位填入 x。首字节开头的 1 的数量即为这个字符占用的字节数,之后的字节会以 10 开头。常用汉字占 3 字节,一些生僻字会占 4 字节。Unicode 标准规定字符上限为 U+10FFFF,但是其实 UTF-8 还可以扩展。UTF-8 的码元(编码单位)为 1 字节。
::cute-table{tuack}
| Unicode 编号 | UTF-8 编码(二进制) | 可编码位数 |
|---|---|---|
| U+0 ~ U+7F | 0xxxxxxx | 7 |
| U+80 ~ U+7FF | 110xxxxx 10xxxxxx | 11 |
| U+800 ~ U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 16 |
| U+10000 ~ U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 21 |
::: :::info[UTF-16 编码规则]
对于 U+0 ~ U+FFFF 之间的字符,其 Unicode 编号即为一个码元;对于 U+10000 ~ U+10FFFF 的字符,把 Unicode 编号减去 0x10000 后的二进制值从右往左、从低位到高位填入 110110xx xxxxxxxx 110111xx xxxxxxxx。这两个码元直接对应的 U+D800 到 U+DFFF 区段被称为代理对,没有字符,所以不会和 2 字节编码的 UTF-16 码元混淆。
由于 UTF-16 码元为 2 字节,所以存在字节序问题。一般通过在文件开头包含一个 U+FEFF 字符的方式声明字节序吗,这个字符被称为 Byte Order Mark (BOM)。U+FFFE 不是字符。
::: :::info[UTF-32 编码规则]
直接将其 Unicode 编号作为一个 4 字节的码元。
:::
常见乱码及其解决方案
:::warning[注意]{open} 当你看见乱码的时候,不要保存,不要保存,不要保存!!! :::
:::info[锟斤拷] 这是由于 GBK 等其他编码的文本按 UTF-8 解码后保存,再按 GBK 解码的结果。根据 UTF-8 编码规则,不是所有字节序列都是合法的 UTF-8 编码。对于不符合规则的序列,UTF-8 用�(U+FFFD,REPLACEMENT CHARACTER,UTF-8:EF BF BD)替代,两个�按 GBK 解码之后就是一个锟斤拷。当看见锟斤拷的时候,你原来的内容已经永远丢失了。你的回忆不断改变着形态,坍缩成了无数的“锟斤拷”,至于该怎么办,你的回忆在哪里停留,问你自己吧。 :::
:::info[记事本联通乱码 (微软和联通有仇?)]
如果因为巧合导致 GBK 编码的字节流正好满足了 UTF-8 编码格式,那记事本会认为它是 UTF-8 从而乱码。Windows 旧版本的“联通”就是如此(新版本改了,但是这个 Bug 还在,只是“联”0xC1AA 是个超长的 UTF-8 编码而已)。你可以用你自己的名字或者其他什么词语去测试。
:::
:::info[烫烫烫、屯屯屯] Visual Code 中,Debug 模式下,未初始化的栈内存会被赋为 0xCC,堆内存赋为 0xCD。GBK 编码中,0xCCCC 为“烫”,0xCDCD 为“屯”。赶紧去检查一下数组越界和野指针吧。 :::
:::info[锘锘锘] 我们知道 UTF-8 没有字节序问题,但是 UTF-8 也仍然可以有 BOM,并且某些软件把它当成普通字符处理。U+FEFF 的 UTF-8 编码为 EF BB BF,其中 0xEFBB 在 GBK 下为锘,BF 会吞掉后一个字节。保存成无 BOM 即可,但是 Windows 的记事本一旦保存就有 BOM 。“锘縫ublic”“非法字符 65279”均属此类。 :::
:::info[一些生僻字夹杂日文] 如“浣犲ソ”,这是 UTF-8 编码的文本按 GBK 解码的结果,重新按 UTF-8 解码即可。 :::
:::info[不要以为原文是 ASCII 就不会乱码] ASCII 可能被记事本误以 UTF-16 解码,如 P2656 的某个数据点。这种情况显示的也是一些汉字。所以遇到乱码的时候多试几种编码。 :::
:::info[折半码] 乱码中有大量帽子字母和一些二分之一之类的分数。这是其它编码按 ISO-8859-1 解码的结果。如果带帽的 e 或 a 很多并且按 3 个为周期出现可以考虑为 UTF-8,其他时候一般考虑为 GBK ,不过还是可以多试几种编码。 :::
:::info[許功蓋] GBK、Big5 等编码的第二字节可能与 ASCII 冲突,当第二字节为 0x5C 的时候与 ASCII 反斜杠冲突,部分编译器不能识别,出现在注释里会导致下一行被注释掉。 :::
:::info[其他] GB 系列编码可能因为丢失一个字节而变为乱码,这个时候考虑用编辑器去掉第一个字节。 :::