跳到主要内容

一文弄懂字符编码,面对乱码不再抓瞎

· 阅读需 5 分钟

我们都知道,计算机起源自美国。那时候的电脑普遍支持 8 bits 运算。如果从零开始编号,128 就足以将英文字母和其他字符全部索引进来。因此,7 位二进制数用作表示字符,即 ASCII 码(8 bit 里面多出来的 1 bit 留出来用于校验等目的)。

后来,计算机卖到了世界各国,混沌之初,一切无序。各国按照各自的语言,在 128 位以上的广阔天地里自由飞翔。

这样就导致了一个问题,美国人发来的英文邮件在西班牙的电脑上有乱码,根本看不懂!

光是想想,每个国家都有自己的小册子,上面记录了 128 位以上的字母(字符)表示,这要求每个计算机都要存储这些小册子,太疯狂了!

历史车轮滚滚向前,Unicode 出场了。它带着将世界上每一个字符都赋予编码的使命来了。

Unicode 的思路是:每一个字母都分配一个抽象的编号,比如'Hello' - U+0048 U+0065 U+006C U+006C U+006F.

好的,现在赋予编号完成了,可是计算机是基于二进制呀,Unicode 如何存储在计算机中呢?

先做一个简单的心算。上面 Hello 的编码,去掉 U+: 00 48 00 65 00 6C 00 6C 00 6F. 每个数字的范围 0-F,二进制需要四位,也就是半个 byte,所以 00 是一个 byte,一个字母 H 是两个 byte!

这时候,美国人跳出来表达了不满:我们的 ASCII 码用的好好的,八位就可以表达所有的字母,现在要我们用 Unicode,每个字母都要用 16 位,那文件大小生生比原来多了两倍!

于是,UTF-8 为了解决上述痛点,依旧将 128 以下的编码用于英文字母,且只用一个字节,只有 128 以上的,才用两个字节存储。这样,对于英语用户之间的文件传递,没有任何影响。

注意概念上的分类:Unicode 和 ASCII 都是字符集。而 UTF-8 是编码方式。

特性字符集 (Character Set)编码方式 (Encoding)
核心问题定义字符和代码点的映射定义代码点如何存储为比特序列
抽象层次抽象(逻辑层):字符和数字的关系具体(实现层):数据如何表示
示例Unicode(U+0041 表示字母 A)UTF-8、UTF-16、UTF-32 等
依赖关系编码方式依赖于字符集字符集需要通过编码方式来实现
计算机表现形式提供标准编号(如 U+0041)提供实际存储格式(如 0x41 或 11000010)

Unicode 只是一个抽象的操作,将任何字符转化为一个 U+字符串。任何计算机上实际显示一个字符串需要一个编码方案的支持。可以想象的 hello 字符串的 unicode 为...,然后某个编码方案 iso-xxx 中找不到这些码,于是显示?或者 �。

最后,作者特别指出一句话:

It does not make sense to have a string without knowing what encoding it uses.

参考文献:

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)