浅谈计算机编码

其实一直想做一个关于计算机字符编码的总结,但是自从把这件事加入到 TODO 中,就开始无限期地搁置。TODO 就是一个诅咒。

为什么要编码

我之前看过一本讲编码的书,太久了,我记得名字叫《编码: 隐匿在计算机软硬件背后的语言》,内容非常深入浅出。从摩斯密码来传送信息开始,一直讲到计算机的编码规则。摩斯密码就是二进制的,计算机就是用电来模拟二进制传输和储存消息。

如果编码不统一的话,就跟语言不不通一样,难道两个计算机用屁股交流吗?所以计算机发明后,为了统一规则,于是了一个 ASCII 的编码规则,ASCII 是美国人制定,ASCII 码一共规定了 128 个字符的编码,比如空格 SPACE 是 32 (二进制 00100000 ) ,大写的字母 A是 65 (二进制 01000001 ) 。这 128 个符号 (包括 32 个不能打印出来的控制符号) ,只占用了一个字节的后面 7 位,最前面的一位统一规定为0。

ascii码表

后来计算机普及到全世界,128个符号完全不够用,所以就用了各个国家就开始用最高位那个闲置的位数来编码,而一些字很多国家,比如中国,就用两个字节编码,两个字节2^16位。常见的中文编码就是GB2312了,我学校的教务系统的网页就用的是这个编码格式。很多古老的中文系统和应用都是GB2312。在powershell窗口里可以看见自己电脑的编码是GBK,GBK是对GB2312的补充,而且GBK向下兼容GB2312,这很关键。再后来就是Unicode一统天下了。

ASCII

单字节的编码规则。ASCII 规定了 128 个字符,最高位为 0。

ISO-8859-1 / Latin-1

单字节的编码规则。这套编码规则由 ISO 组织制定。是在 ASCII 码基础上又制定了一些标准用来扩展 ASCII 编码,即 00000000 (0) ~ 01111111 (127) 与 ASCII 的编码一样,对 10000000 (128) ~ 11111111 (255) 这一段进行了编码,如将字符§编码成10100111 (167) 。ISO-8859-1 编码也是单字节编码,最多能够表示 256 个字符。Latin1 是 ISO-8859-1 的别名,有些环境下写作 Latin-1 。之前我暑期实践的时候,老师给过我一个很久以前 NASA 的数据,大概是上个世纪八九十年代的,就是 Latin-1 编码的,现在我感觉应该没什么人用这个了。

GB2312

两个字节的编码规则。这个是简体中文的编码,涉及到区位码,因为中文编码有一套自己的规则,我不细写了。

GBK

两个字节的编码规则。GB2312 的补充,完全兼容 GB2312 的,而且还加了繁体。就是一套更好的中文编码。

BIG5

两个字节的编码规则。繁体中文的编码,是台湾的一套编码规则,但是和GBK是不兼容,就是说台湾要是用 BIG5 ,大陆用 GBK 是会出现乱码的。

Unicode

Unicode 并不是一种编码,而是一个标准。window 下记事本说保存为 Unicode 其实是保存 UTF-16。地球上所有字符都可以在Unicode 表中找到对应的唯一码点。包括 emoji 也是一个字符。Unicode 只是规定了符号集,并没有规定怎么储存。所以有了各种Unicode 的实现。

UTF-8

UTF-8 是一种变长编码方式,使用 1-4 个字节进行编码。UTF-8 是 Unicode 一种具体的编码实现。终于到了这个现在几乎是国际间标准的编码了。现在基本流行的网页,应用,文件都是 utf-8。utf-8 完全兼容 ASCII。别的符号,比如中文通常是3个字节来储存的。Unicode 码点如何转化为 UTF-8 编码,可以参照如下规则:

“对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。 ”
“对于 n 字节的符号 (n>1) ,第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。”

总结的编码规则如下:
utf-8编码规则

说明: 字符 ’A’ 的 Unicode 码点为 65 (十进制) ,根据上表,在第一行范围,则字符 ’A’ 的 UTF-8 编码为 01000001,中文字符’李’的 Unicode 码点为 26446 (十进制) ,二进制为 01100111 01001110,十六进制为 674E。根据上表,在第三行范围,则将’李’二进制代码从低位到高位依次填入x中,不足的填入 0。得到 UTF-8 编码为 11100110 10011101 10001110,即E69D8E (十六进制) 。

UTF-16 / UTF-32

UTF-16 使用 2 或 4 个字节编码。UTF-32 对每个字符都使用 4 字节。

UTF-16 总的来说是用 1 个 16 位(一个 16 位能表示 65536 个字符)或者两个 16 位来编码。绝大多数都是 Basic Multilingual Plane (BMP)。然后一个 16 位已经不能表示 Unicode 所有的字符了,所以使用的代理对的方式来编码。要怎么知道这个 UTF-16 文本是 1 个 16 位还是两个 16 位呢。

于是把 U+D800 - U+DFFF 中间给挖空,这个区域内不表示任何字符。简单来说如果是 U+D800 - U+DFFF 开头的,都是代理对的形式,然后进行一些换算(具体就不展开讨论了,具体可以看参考资料)。然后就变成 2 个 16 位表示一个字符。看一张 wiki 上的图。

utf-16

UTF-16 is used internally by systems such as Windows and Java and by JavaScript, and often for plain text and for word-processing data files on Windows. It is rarely used for files on Unix/Linux or macOS.

是 Java 和 JavaScript 的字符编码形式,所以我觉得还是要讨论一下的。

BOM机制

因为 utf-8 第一个字节就储存了这个字符的长度信息,所以 utf-8 理论上是不需要 BOM 机制,但是在 windows 上的记事本程序中,会在保存 utf-8 时会在开头加上 EF BB BF 表示这是 utf-8 编码的。但是在 linux 上和平时用的别的代码编辑器上是没有这个 BOM。虽然 Unicode 官方说 utf-8 是需要加上这个头的,但是主流就是不加这个头的,而理论上也不需要这个头。utf-8 不加BOM 头似乎才是事实上的国际标准。微软遵守了这个标准似乎成了异类。

utf-16 是两个字节或四个字节编码的。字符 ’A’ 的 Unicode 码点为 65 (十进制) ,十六进制表示为 41,。根据规则,UTF-16 采用 2 个字节进行编码。那么问题又来了,知道了采用两个字节编码,并且我们也知道计算机是以字节为单位进行存储,这两个字节应该表示为 00 41(十六进制)?或者是 41 00 (十六进制) 呢?计算机不知道这个到底是 2 个或者 4 个,所以需要加上一个 BOM 头来告诉计算机。表示为 00 41 意味着采用了大端序 (Big endian) ,而表示为 41 00 意味着采用了小端序。

所以 BOM 机制就是在文件头加入一些控制信息,具体是采用大端序,则在文件前加入 FE FF,采用小端序,则在文件前加入 FF FE。这样,当计算开始读取时发现前两个字节为 FE FF,就表示之后的信息采用的是小端序,反之,则是大端序。这样计算机就知道怎么读取这个字符了。

ANSI

windows 上的记事本还有一个就是 ANSI 的储存,这个其实就是用计算机默认的编码来存储的,像我的电脑系统是 GBK,储存就是用 GBK储存。

既然 Unicode 这么秀,为什么不都用 Unicode 呢,还是速度和空间的问题,GBK 的中文两个字符,utf-8 中文通常是三个字符,这个差距就在中文很多的系统中就很明显的。

虽然说我的电脑系统是 GBK 编码,并不是全都是 GBK,网上有人做过实现,改了系统的编码,有些的系统的东西并未显示乱码,是因为系统中一些东西是 Unicode 编码和解码的。

utf8mb4

其实也是 utf-8,但是有些软件对 utf8 的编码只支持到 3 个字节,所以 emoji 表情储存不了。例子就是 mysql,将编码改成utf8mb4 就可以了。

url编码

url 编码并不是新的编码标准,而是一个规定。

网络标准 RFC1738 做了规定:

“只有字母和数字[0-9a-zA-Z]、一些特殊符号”$-_.+!*’(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”

别的符号都是经过编码,所以地址栏中没有出现中文和一些很奇怪的符号,那这些东西是怎么编码的呢。RFC 文档是建议用 utf-8 编码的,如”中文”使用 UTF-8 字符集得到的字节为 0xE4 0xB8 0xAD 0xE6 0x96 0x87,经过 Url 编码之后得到”%E4%B8%AD%E6%96%87”。一般情况下网页的 head 中都会加一句:

1
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

会让浏览器用 utf-8 去渲染此文档。但是如果你改成 gb2312,提交表单的时候就会用 gb2312 编码,但是可能会出现一些问题,因为现在几乎所有的国际 web 都是用 utf-8 为标准的,要是硬要调皮的话,可能会撞到墙上。

unicode编码

有时候经常遇到一些奇怪的字符,比如 \u6e05\u534e\u5927\u5b66 这种,其实 \u 表示这是 unicode,后面的 6e05 就是表示在 unicode 的码点,只要用 unicode 标准下的编码规则都可以解析的,\u6e05\u534e\u5927\u5b66 解析就是清华大学。很多编程语言都可以直接解析。

Base64编码

Base64 是一种任意二进制到文本字符串的编码方法,常用于在 URL、Cookie、网页中传输少量二进制数据。有时候上传头像什么的就会用到 base64。base64 当然可以做简单的加解密。

参考链接:

总结

学校每次只有晚上8、9点才有热水,每次这个时间点我都在干别的事。所以今年还没洗过澡。明天一定要洗澡。