为什么有的时候打开一份文件会乱码?

为什么有时候程序里写的明明是正常中文,控制台打印出来后确实一堆乱七八糟的符号?

要想知道这个问题的答案,就必须了解——字符集编码。

一、为什么会有字符集编码?

在计算机系统中,为了让计算机能够识别和处理人类语言交流中的文字,我们需要使用文字编码的方式。

文字编码是将每个字符转换成一串特定的二进制数的过程。当计算机读取到这串二进制数时,它就能够理解我们需要显示的文字内容。

这种编码方案允许计算机将人类语言转换为计算机能够理解的二进制表示,从而实现文字的输入输出处理

通过为每个字符分配一个唯一的二进制代码,就像为每个人分配一个身份证号码一样,计算机可以成功识别和表达文字信息。

这种文字编码的过程是计算机的关键基础之一,使得计算机成为人类之间信息交流的重要工具。

二、常见的字符集编码方式

2.1 ASCII

ASCII(American Standard Code for Information Interchange)是一套最早由美国人开发的字符编码方案。

由于最初计算机只在美国使用,并且英语字母、数字和一些常用符号是最主要的字符,ASCII 字符集仅包含 128 个字符。

为了表示这些字符,每个字符使用 8 位二进制位(1 字节)进行编码。

尽管实际只需要 7 位二进制位来表示这些字符(因为 $log_{2} 128 = 7$ ),但计算机系统中常用的存储单位是字节(8 位),因此为了保持一致性,ASCII 码采用 8 位编码的方式。

补充说明: 在计算机中,信息被存储和处理为二进制形式,这是由物理性质和计算机架构的基础规则所决定的。常见的计算机系统通常以 2 的幂次方作为最小的信息单位大小,例如 2、4、8、16、32、64 等字节数,以方便计算、存储、操作和统计。最初的计算机性能和存储容量有限,因此采用了 4 位 BCD(二进制编码十进制)编码。BCD 编码最早出现在打孔卡上,用于输入十进制数字。随着电子计算机时代的到来,计算机性能和存储容量得到了显著提升,为了更有效地表示和处理更多字符和符号,计算机开始使用 8 位(即 1 字节)作为编码单位,并且这种编码方式一直沿用至今。使用 8 位编码单位可以提供更大的灵活性和更大的编码空间,使得计算机可以表示更广泛的字符集和数据类型。

图2.1.1 ASCII 码表

2.1.1 ASCII 的编码方式

ASCII 编码方案相对简单而粗暴,它将需要编码的字符按照顺序一一排列,并为每个字符分配一个逐渐增加的编号,也称为码点。

ASCII 编码使用 7 位二进制来表示每个字符的编码,这样可以确保每个字符的编码在 0 到 127 的范围内。

最后,通过将这些编号(码点)转为二进制形式,就可以得到对应字符在 ASCII 编码中的编码。

例如,字母 “A” 的 ASCII 码点是 65(在大写字母的范围内),它的二进制编码是 01000001;数字 “5” 的 ASCII 码点是 53,它的二进制编码是 00110101。通过这种方式,计算机可以使用固定长度的二进制编码来表示不同的字符,从而实现对字符的表示和处理。

图 2.1.2 ASCII 编码规则

注意:最初的 ASCII 码多出一位没被使用,所以 ASCII 规定这个最高位必须为 0,从而保持统一。

2.1.2 ASCII 的编码扩展

ASCII 编码方案的简单性使得它在早期计算机系统中得到了广泛应用,尤其是在英语为主要语言的国家和地区。

然而,随着计算机的发展和国际间信息交流的增加,ASCII 编码的 128 个字符已经无法满足多地区范围内多语言文本的需求。

因此,ASCII 后来将多出的一位利用了起来,进行 ASCII 编码的扩展,使其他欧洲国家比如使用法语、葡萄牙语等语言的国家也能够使用计算机。

图2.1.3 ASCII 编码扩展

注意:也就是说最新的 ASCII 使用了 8 位(1 字节)进行编码,共计包含 256 个字符。

2.2 GBK

当计算机传入中国后,中文编码成为一个重要且具有挑战性的问题。因为中文字符的数量庞大,一个汉字由于有成千上万个字符,使用传统的 ASCI 编码无法涵盖全部汉字。

为了解决这个问题,中国制定了一套中文字符编码方案,称为 GBK 编码(即《汉字内码扩展规范》)。GBK 编码是对汉字的字符集编码,它在 ASCII 编码的基础上进行了扩展,使得计算机能够表示和处理更多的中文字符。

图2.2.1 GBK 编码示意图

2.2.1 GBK 的编码方式

GBK 编码采用双字节编码方案,每个中文字符使用两个字节(16 位)进行编码。这样,GBK 编码可以表示超过 20,000 多个中文字符,包括简体中文和繁体中文。GBK 编码在计算机系统中得到广泛应用,并成为中文信息处理的重要基础。

但是要注意的是,为了兼容 ASCII 编码的同时减小存储消耗,所以对于 ASCII 码的字符依旧采用单字节(8 位)编码方案。

那么,问题就来了:当中英文混合时,机器如何识别读取到的一个字节的信息到底是一个 ASCII 码,还是半个中文字符编码呢?

为了解决这个问题,GBK 规定所有的中文字符的二进制编码最高位必须为 1

例如,中英文混合的编码可以参考下图:

图 2.2.2 “我A你”编码示意

2.3 Unicode

然而,即便是 GBK 编码,仍无法涵盖所有的中文字符和其他语言字符。为了进一步满足全球范围内的字符需求,后来出现了 Unicode 编码(亦称,万国码)方案,它拥有更广泛的字符集,包括几乎所有的世界语言字符。Unicode 成为了现代计算机系统中最常用的字符编码方案之一。

图2.3.1 Unicode 官网

2.3.1 UTF-32 的编码方式

UTF-32 使用 32 位(4 字节)无符号整数来表示每个字符,每个编码单元都精确地对应一个 Unicode 字符。

UTF-32 编码中的每个编码单元都由 32 个二进制位组成,没有保留位。下面是 UTF-32 编码的特性和格式:

  • UTF-32 使用固定长度的 32 位(4字节)编码单元,每个编码单元可以表示 0 到 0x10FFFF(共 1,114,111)范围内的 Unicode 字符。
  • 每个 Unicode 字符对应一个唯一的编码单元,即每个字符都用 4 字节表示。
  • UTF-32 编码是无字节序的,不需要字节顺序标记(BOM)来指示字节序。
  • UTF-32 编码中的字符按其 Unicode 代码点顺序存储,从最低有效位(LSB)到最高有效位(MSB)。
  • UTF-32 编码中的编码单元没有保留位,每个位都被用于表示字符的代码点。

缺陷:这个全部编码的想法是好的,但是现实很骨感。因为 UTF-32 采用固定的 32 位二进制数进行编码,虽然囊括了世界上几乎所有语言的大部分字符,但是却造成了严重的存储资源浪费

例如,相对于 ASCII 中的字符,其在 UTF-32 中体积扩大了 3 倍;而相较于 GBK 中的中文字符,其也扩大了 1 倍,这当然不被大部分国家所接受。

2.3.2 UTF-8 的编码方式

为了解决 UFT-32 存储资源浪费的问题,于是推出了 UTF-8 编码方案。

在 UTF-8 中采用了可变长编码方案,共分为四个长度区:1 字节区2 字节区3 字节区4 字节区

具体可以表示为:

图2.3.2 UTF-8 编码示意

三、为什么会乱码?

当我们在计算机上显示的文本出现乱码时,主要是因为计算机无法正确解读或显示文本所使用的字符编码

计算机内部处理文本时,使用了一套字符编码来将字符映射为二进制数据。这样,计算机可以存储、传输和处理文本。不同的文本编码方式(如ASCII、UTF-8、GBK等)使用了不同的规则来映射字符和二进制数据

现在,如果一个文本文件的编码方式和我们打开它的方式不匹配,就会出现乱码。举个例子,假设文本文件使用 UTF-8 编码,但我们却使用 GBK 编码打开它。由于 UTF-8 和 GBK 的编码规则不同,计算机会错误地解读字符的二进制表示,导致显示出乱码。

图3.1 乱码

乱码也可能发生在字符编码方式不支持的字符上。比如,一个文本文件使用 ASCII 编码,但其中包含了一些非 ASCII 字符(如中文字符)。由于 ASCII 只能表示英文字符和一些特殊符号,并不支持中文字符,因此当我们尝试用 ASCII 编码打开这个文件时,中文字符就会被错误地解读为其他字符或乱码显示。

:此外,乱码也可能发生在文本传输或存储过程中发生了错误的字符编码转换。如果在文本传输过程中,字符编码的转换出现了错误或遗漏,就会导致接收方无法正确解读文本,最终呈现出乱码。

总之,乱码的出现是由于字符编码的不匹配不支持或转换错误等原因所致。为了避免乱码,我们需要确保使用正确的字符编码方式打开、保存和处理文本,并在文本传输过程中保持一致的字符编码。