在中文的语言环境里,身为程序员的我们一定会遇到过中文乱码的情况,究其原因就是字符编码的问题。在没有深入理解其原理之前,会觉得中文编码问题比较谜,莫名其妙地乱码,又稀里糊涂地好了。
字符编码是计算机技术的基石,本文希望帮助大家彻底梳理清楚字符编码问题,不仅知其然,还知其所以然,摆脱被中文乱码支配的感觉。
在讲解中文编码问题之前,我们需要先讲讲英语编码,其解决方案是 ASCII。
ASCII
ASCII 的英文全称是 American Standard Code for Information Interchange,中文意思是美国信息交换标准代码,是基于拉丁字母的一套计算机编码系统,使用 8 位二进制表示字符。
在计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有 0 和 1 两种状态,因此 8 个二进制位就可以组合出 256 种状态,这被称为一个字节(byte)。
换句话说,一个字节可以表示 256 种不同的状态,每一个状态对应一个符号,也就是 256 个符号,从 0000 0000 到 1111 1111,其数量计算公式:。
列举一部分 ASCII 码表,如下所示:
英语是由 26 个基本拉丁字母、阿拉伯数字和英式标点符号组成,因此用 128 个符号就足够了,但 ASCII 码对于其他一些复杂的语言,就力不从心了,比如:汉字大约将近 10 万个(虽然没有准确的数字,但日常使用汉字也有几千字)。
一个字节只能表示 256 种符号,肯定是不够的,就必须使用多个字节表达一个符号。为了正确显示中文字符,在 1981 年 5 月 1 日,由中国国家标准总局发布了《信息交换用汉字编码字符·基本集》,通常简称 GB。
GB 类
中国大陆几乎所有的中文系统和国际化的软件都支持 GB2312。GB2312 是简体中文常见的编码方式,使用两个字节表示一个汉字,所以最多可以表示 个符号。
GB2312 标准共收录 6763 个汉字,其中一级汉字(常用字)3755 个,二级汉字(较不常用)3008个,同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。
GB2312 基本满足了计算机处理简体汉字的需求,所收录的汉字覆盖了 99.75% 的使用频率,但对于罕见字和繁体字,GB2312 就不能处理了。因此发明了后来的 GBK 和 GB18030。它们之间的关系如下图所示:
GBK 编码是 GB2312 编码的超集,向下完全兼容 GB2312,兼容的含义是不仅字符兼容,而且相同字符的编码也相同。而 GB18030 编码向下兼容 GBK 和 GB2312,GB18030 编码是变长编码。
但很多像 GB 类的编码方式都有一个共同的问题,允许计算机处理双语环境,即拉丁字母和本地语言,却无法同时支持多语言环境,即多种语言混合的情况。Unicode 就是为了解决这个问题而诞生的方案。
Unicode
世界上存在着多种语言,比如:西班牙语、韩语、俄语等等,它们也都分别有各自的编码方式,所以同一个二进制数字可以被解释成不同的符号。如果想要正确的打开一个文本文件,就必须知道它的编码方式,否则就会出现乱码。
假如有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,一种所有符号的编码。
Unicode 伴随着通用字符集的标准而发展,当前最新的版本为 2019 年 5 月公布的 12.1.0,已经收录超过 13 万个字符。Unicode 涵盖的数据除了视觉上的字形、编码方式、标准的字符编码外,还包含了字符特性,如大小写字母。
然而,Unicode 只是一个符号集,不代表计算机里的编码,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。因此,导致了两个问题:
计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
英文字母只用一个字节表示就够了,但按照 Unicode 规定,每个符号要用 3 个或 4 个字节表示,那么英语文本的存储空间将扩大 3 到 4 倍,是极大的浪费。
随着互联网的发展,不同国家的信息越来越多地在网络中传播,强烈需要一种统一的编码方式,UTF-8 就是在互联网上被广泛使用的一种 Unicode 实现方式。
UTF-8
再次强调一下,UTF-8 是 Unicode 的实现方式之一,并不是唯一,也不等同于 Unicode。除了 UTF-8,还有 UTF-16 和 UTF-32,只是很少被使用。
UTF-8 的特点是对不同范围的字符使用不同长度的编码,它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。其编码规则很简单:
对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
对于 n 字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10,剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
Unicode 与 UTF-8 的字节对应关系:
以“爱”为例,其的 Unicode 是 U+7231,是在 U+0800~U+FFFF 范围内,所以采用 3 个字节进行编码,其二进制为 01110010 00110001。那么用 UTF-8 表示,则如下图所示:
后记
计算机操作系统中的编码:
Windows下中文的默认编码是 GBK(GB2312)。
Linux 下中文的默认编码是 UTF-8。
如果使用的是 Linux 系统,可以通过如下命令,查看系统中文编码:
1echo $LANG
2en_US.UTF-8
如果想要查看文件的原始编码,并且转换编码,可以使用 enca 命令,可以通过 apt-get install enca 进行安装。
1enca -L zh_CN <file> # 查看文件的编码
2enca -L zh_CN -x UTF-8 <file> # 将文件编码转换为UTF-8编码
3enca -L zh_CN -x UTF-8 <file_1> <file_2> # 保留原始文件
字符编码选择建议:
只有英文,选择 ASCII。
主要存中文,对存储大小比较敏感,选择 GB2312。
通用性第一,处理简单,选择 UTF-8。