文章

关于编码的那些事

本文内容来源于网络,略有删减修改,如有侵权,请联系我。


本文引用及参考的地址:
Unicode 和 UTF-8 有什么区别?
网页编码就是那点事
一文帮你彻底弄懂编码/字节/字符
字符编码笔记:ASCII,Unicode 和 UTF-8

零、编码范围图

image.png

一、ASCII

在计算机内部所有信息都是二进制值,每一个二进制(bit)有01两种状态,因此 8 个二进制位就可以组合出 256(2 的 8 次方)种状态,这被称为 1 个字节(byte)。也就是说,1 个字节一共可以表示 256 种不同的状态,每一个状态对应一个符号,就是 256 个符号,从 00000000 到 11111111。

1 字节(byte) = 8 位(bit)

开始计算机只在美国用。他们把其中编号从 0 开始的 32 种状态分别规定了特殊的用途,一但终端、打印机遇上的字节是这些约定好的编号被传过来时,就要做一些特定的动作。遇上 0×10,终端就换行;遇上 0×07,终端就向人们嘟嘟叫;例好遇上 0x1b,打印机就打印反白的字,或者终端就用彩色显示字母。他们把这些 0×20 以下的字节状态称为控制码

他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第 127 号,这样计算机就可以用不同字节来存储英语的文字了。他们把这个方案叫做 ANSIASCII 编码(American Standard Code for Information Interchange,美国信息互换标准代码)。

二、编码的巴比伦塔命题

后来,就像建造巴比伦塔一样,世界各地都开始使用计算机,但是很多国家用的不是英文,世界各国为了可以在计算机上保存他们的文字,他们决定采用 127 号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用到的横线、竖线、交叉等符号,一直把序号编到了最后一个状态 255。从 128 到 255 这一页的字符集被称扩展字符集。从此之后,原有的编码方法,已经再也放不下更多的字符了。

等中国人得到计算机时,已经没有可以利用的字节状态来表示汉字了,况且有 6000 多个常用汉字需要保存。于是我们就自主研发,把那些 127 号之后的奇异符号们直接取消掉。规定一个小于 127 的字符的意义与原来相同,但两个大于 127 的字符连在一起时,就表示一个汉字,前面一个字节(称之为高字节)范围从 0xA1 到 0xF7,后面一个字节(称之为低字节)范围从 0xA1 到 0xFE,这样我们就可以组合出大约 7000 多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符,而原来在 127 号以下的那些就叫半角字符了。于是就把这种汉字方案叫做 GB2312。GB2312 是对 ASCII 编码的中文扩展。

但是中国的汉字太多了,后来还是不够用,于是干脆不再要求低字节一定是 127 号之后的内码,只要高字节是大于 127 号之后的编码就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。扩展之后的编码方案被称为GBK标准,GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号。后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。

这一系列汉字编码的标准统称为DBCS(Double Byte Charecter Set 双字节字符集)。在 DBCS 系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于 127 的,那么就认为一个双字节字符集里的字符出现了。

三、Unicode

因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂别人的编码,谁也不支持别人的编码。中国人想让电脑显示汉字,就必须装上一个汉字字符系统,专门用来处理汉字的显示、输入问题,装错了字符系统,显示就会乱了套。就在这时,一个叫ISO(国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们叫它Universal Multiple-Octet Coded Character Set,简称UCS,俗称Unicode

世界上存在着很多种编码方式,同一个二进制值可以被解释成不同的符号。因此,要想打开一个文本文件,就必须要知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。

但值得注意的是 Unicode 只是一个符号集,规定了如何编码(也就是规定了每个字符的编号),但没规定如何存储。

如果 Unicode 统一规定,每个符号用 3 个或 4 个字节表示,那么每个英文字母前都必然有 2 到 3 个字节是 0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍。

所以导致的结果是:

  1. Unicode 在很长一段时间内无法推广,直到互联网的出现。

  2. 出现了多种 Unicode 的存储方式。

四、UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。于是面向传输的众多UTF(UCS Transfer Format)标准出现了。UTF-8就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。强调一下,UTF-8 只是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节来表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

  2. 对于 n 字节的符号(n > 1),第 1 个字节的前 n 位都设为 1,第 n + 1 位设为 0,后面字节的前两位一律设为 10。剩下的二进制位为这个符号的 Unicode 码。

Unicode符号范围(十六进制)UTF-8编码方式(二进制)
00000000 - 0000007F0XXXXXXX
00000080 - 000007FF110XXXXX 10XXXXXX
00000800 - 0000FFFF1110XXXX 10XXXXXX 10XXXXXX
00010000 - 0010FFFF11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是 0,则这个字节单独就是一个字符;如果第一位是 1,则连续有多少个 1,就表示当前字符占用多少个字节。

比如:以汉字“严”为例


“严”的 Unicode 是 4E25(01001110 00100101),根据上表,可以发现 4E25 在第三行的范围内(00000800 - 0000FFFF),因此“严”的 UTF-8 编码需要三个字节,即格式是 1110XXXX 10XXXXXX 10XXXXXX。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的 X,多出的位补 0。这样就得到了,严的 UTF-8 编码是 11100100 10111000 10100101,转换成十六进制就是 E4B8A5。

五、记事本

Windows 内置的记事本程序notepad.exe,在保存对话框的最底部有一个编码的选项,里面有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8。

image.png

  1. ANSI 是默认的编码方式。对于英文文件会采用 ASCII 编码,对于简体中文文件,若是 Windows 简体中文版会采用 GB2312 编码,若是 Windows 繁体中文版则会采用 Big5 编码。

  2. 记事本程序中的 Unicode 编码指的是使用 UCS-2 编码方式,即直接用两个字节存入字符的 Unicode 编码方式,这个选项用的 little endian 格式。little endian 的涵义见下文。

  3. Unicode big endian编码与上一个选项相对应。big endian 的涵义见下文。

  4. UTF-8编码,也就是上述提到的编码方式。

六、Little endian 和 Big endian

上述提到UCS-2格式可以存储 Unicode 码(码点不超过 0xFFFF)。以汉字“严”为例,Unicode 码是 4E25,需要用两个字节存储,一个字节是 4E,另一个字节是 25。存储的时候,4E 在前,25 在后,就是Big endian方式;25 在前,4E 在后,这是Little endian方式。

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。

第一个字节在前,就是大头方式(Big endian),第二个字节在前就是小头方式(Little endian)。

那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?

Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做零宽度非换行空格(zero width no-break space),用 FEFF 表示。这正好是两个字节,而且 FF 比 FE 大 1。

如果一个文本文件的头两个字节是 FE FF,就表示该文件采用大头方式;如果头两个字节是 FF FE,就表示该文件采用小头方式。

七、实例

打开记事本程序 notepad.exe,新建一个文本文件,内容就是一个“严”字,依次采用 ANSI,Unicode,Unicode big endian 和 UTF-8 编码方式保存。

然后,用文本编辑软件 UltraEdit 中的十六进制功能,观察该文件的内部编码方式。

  1. ANSI:文件的编码就是两个字节 D1 CF,这正是“严”的 GB2312 编码,这也表示 GB2312 是采用大头方式存储的。

  2. Unicode:编码是四个字节 FF FE 25 4E,其中 FF FE 表明是小头方式存储,所以“严”字的真正的编码是 4E 25。

  3. Unicode big endian:编码是四个字节 FE FF 4E 25,其中 FE FF 表明是大头方式存储。

  4. UTF-8:编码是六个字节 EF BB BF E4 B8 A5,前三个字节 EF BB BF 表示这是 UTF-8 编码,后三个 E4B8A5 就是“严”字的 UTF-8 编码方式的具体编码,它的存储顺序与编码顺序是一致的。

八、延伸阅读

License:  CC BY 4.0