char类型只包含1个字节的存储空间,而1个字节最多能表达256种不同的值。如果只表达英文字符及其标点符号,1个字节足够。但其它文字,比如中文,其“字符”有数万之多。在编码其它语言文字时,可能会使用到不同的多字节编码方案。

版权声明

本文可以在互联网上自由转载,但必须:注明出处(作者:海洋饼干叔叔)并包含指向本页面的链接。

本文不可以以纸质出版为目的进行改编、摘抄。

1. 文本编码与解码

  计算机是二进制的,它的文件和内存严格地说,只能存储和处理比特流,每个比特对应二进制的1位。由于这些二进制位按8位一组被分隔成字节,我们也可以认为计算机只能存储和处理字节流,用大白话说就是一个接一个的字节。

  所以,当我们把由多个字符构成的文章存储到计算机里的时候,存入到内存或者硬盘的并不是文字本身,而是这些文字字符的对应编码。我们以ASCII码为例为说明相关过程。ASCII码是所谓美国标准信息交换代码,它用一个字节来表示一个字符。一个字节有8位,共256种组合,用来表示英文字符、数字及标点绰绰有余。

  ASCII码的完整码表请见: 美国信息交换标准交换代码表 - Python,C/C++ Club (codelearn.club)

  为便于讨论,我们在Windows下用记事本编辑了如下内容的文件,并保存为hw.txt。

image-20221101165544932

  查看hw.txt的文件属性,可见该文件事实上占据了11个字节的空间。

image-20221101165525203

  回顾hw.txt的文件内容,其中的”Hello World”连同其中的空格一起,正好11个字符。

image-20221101175008387

注意:000000 000013表示序号,不是文件的构成内容。

  在Windows命令行下,使用如上图所述的od命令可以查看文件hw.txt的原始内容,其内的11个字节的值用16进制表示出来就是:

1
48 65 6C 6C 6F 20 57 6F 72 6C 64

  其中,48在ASCII码里对应’H’,65对应’e’,20对应空格,64对应’d’…

文本编码

  将文本存储至计算机内时,需要先把文本中的字符逐一转换成对应的编码,这一过程被称为”文本编码“。比如,文本”Hello World”被编码成字节流”48 65 6C 6C 6F 20 57 6F 72 6C 64“。

文本解码

  对于Windows记事本而言,文件hw.txt就是包含11个字节的字节流,需要将这11个字节按照码表映射至文本,再显示出来,这一过程被称为”文本解码“。比如,字节流”48 65 6C 6C 6F 20 57 6F 72 6C 64“被转换成”Hello World”。

编码方案

  同一文字符号,在不同的编码方案内,其对应的字节或字节序列很可能不同。

  • ASCII码使用一个字节表示一个字符,而1个字节仅有8个比特,256种组合,因此,ASCII码只能表示英文字母、数字及符号。
  • GB2312-80(汉语国标)使用双字节来表示一个汉字符号,同时,为了兼容英文,也用单字节来表示英文字符。理论上,GB2312-80可以较好地支持同一个文件内的中英文混合表达。但随着国际交流的日益频繁,在同一个网页或者文档里, 英、法、日、中、泰、韩等多国语言交替使用的场景屡见不鲜,此时,GB2312-80就不够用了。 后来国家又出手搞了GB18030… 但事实上未获得广泛应用。

  为了促进世界大同,有人弄出了Unicode®,这种编码方案可以在同一文档/网页内容纳多国语言没有歧义地混合使用。

2. Unicode®

  粗略地理解,Unicode就是一张表,这张表将世界上”所有“的文字字符,甚至一些表情符号(emoji表情)对应成互不相同的整数。Unicode®自发明以来,一直在不断修订中,以收录”新“的语言符号。至2022年9月13日止,Unicode® 15.0.0总共收录了149,186个字符。Unicode的表太长,下表给出了其中的几个示例。

表1 Unicode编码示例
符号 编码(整数,16进制) 说明
B 0042 英文字符
Ê 00CA
ڣ 06A3 阿拉伯语字符
0975 未知语言字符
1170 韩语符号
222B 数学符号之积分
26FA 帐篷
3056 日语假名
4EBA 汉字

  完整的Unicode®表格可以通过下述链接查询:https://unicode-table.com/en/

  Unicode® 15.0.0的标准原文见:https://www.unicode.org/versions/Unicode15.0.0/

3. UTF-8

  UTF-8可以认为是Unicode的一种实施方案,通过UTF-8可以把一个字符对应的Unicode编码(一个整数)转换成一个字节序列,这个字节序列可能包含1个、2个、3个或者4个字节。

表2 Unicode编码与UTF-8的转换关系
Unicode编码(16进制) UTF-8字节序列 说明
000000 ~ 00007F 0xxxxxxx 单字节, 含7个x
000080 ~ 0007FF 110xxxxx 10xxxxxx 2字节,含11个x
000800 ~ 00FFFF 1110xxxx 10xxxxxx 10xxxxxx 3字节, 含16个x
010000 ~ 10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4字节, 含21个x

转换举例

  • 对于英文字母、数字及英文符号,其Unicode编码与ASCII是一致的, 其值≤0x7F,按上表的规则,仍使用单字节编码。比如,’B’的Unicode编码为00004216,转换成7位二进制即为10000102,逐一替换上表第1行中的7个x,即为010000102,仍是4216,与’B’的ASCII码值完全相同。

  • 对于阿拉伯母字符’ڣ’,其Unicode编码为06A316,其值满足上表第2行的条件,按规则应使用2字节编码。06A316转换成11位二进制即为110101000112,逐一替代上表第2行中的11个x,即为11011010 101000112,按16进制表达即为DA A316

  • 对于汉字’人’,其Unicode编码为4EBA16,其值符合上表第3行的条件,按规则应使用3字节编码。4EBA16转换成16位二进制即为01001110 101110102,逐一替代上表第3行中的16个x,即为11100100 10111010 101110102,按16进制表达即为E4 BA BA16

3. UTF-8编码示例

  我们使用Windows记事本编辑了一个”多国语言文件”,其中包含英文字母“B”,阿拉伯母字母“ڣ”,以及汉语的“人”,然后按UTF-8编码保存为文件mixed.txt。

image-20221101203608750

  注意保存时选择UTF-8编码。

image-20221101204117661

  查看mixed.txt文件属性,发现其尺寸为6个字节。

image-20221101203801490

  再次使用od命令查看文件mixed.txt的原始内容,结果如下:

image-20221101203942296

注意:000000, 000006是序号信息,不是文件的构成部分。

  其中的”42 DA A3 E4 BA BA”与前述分析完全相同。编码程序完全按照上述UTF-8规则将文字”Bڣ人”转换为字节序列”42 DA A3 E4 BA BA”,然后把这个字节序列共6个字节写入文件mixed.txt。

4. UTF-8的模拟解码

  当我们用记事本将文件mixed.txt中的原始内容读出时,其就是由6个字节构成的字节序列:42 DA A3 E4 BA BA16。 作为一个文字编辑软件,记事本需要通过UTF-8解码将上述字节序列查表转换成”Bڣ人”,然后再将其显示出来。字符的显示过程涉及绘图、字体等内容,此处略去不提。

  我们可以假想出记事本软件内部的解码程序的执行过程:

  • 解码程序从前至后逐一扫描字节序列,首先遇到4216,即010000102,该字节的最高位为0,根据表2,该字节为1字节编码,其单独表示一个Unicode字符,按表2第1行的规则从中取得后7个比特,其值仍为4216,查Unicode表,即得英文字符’B’。
  • 解码程序继续扫描,得DA16,即110110102,其高3位为110,根据表2,该字节连同后面的A3合为一个2字节编码,按表2第2行的规则从中抽取11个比特,得110101000112,即06A316,查Unicode表,即得阿拉伯语字符‘ڣ’。
  • 解码程序继续扫描,得E416,即111001002,其高4位为1110,根据表2,该字节连同后面的BA BA合为一个3字节编码,按表2第3行的规则从中抽取16个比特,得01001110 101110102,即4EBA16,查Unicode表,即得汉字’人’。
  • 字节序列结束,解码程序终止。

  从上述解码过程,读者不难看出,UTF-8的编码是没有歧义的,即特定的字节序列与特定的文本内容一一对应。在以汉字为主的文档里,使用UTF-8编码,一个汉字需要3个字节来表达,而使用GB2312-80,一个汉字只需要2个字节来表达。相较于后者,UTF-8编码的文 档需要占据更多的存储空间以及网络带宽。

5. UTF-16, UTF-32

  作者觉得没什么意思,UTF-8现在是主流,读者在安装Linux,设计网页时,如果选用UTF-8编码,是最安全的。

  事实上,除了UTF-8,GB2312-80,ASCII,世界还有很多很多乱七八糟的编码方案…

6. 为什么会乱码

image-20200216161744112

  读者或许在程序开发的过程中看到过如上图所示的乱码现象。所谓乱码,本质是文本编码与文本解码发生了错位,如果把一个GB2312-80编码的文档按照UTF-8来进行解码,自然会发生错误,形成所谓“乱码”。