跳转至

8.4 文件与目录——字符集与编码

内容梗概

本节课讲解计算机领域绕不过去的问题:字符集与字符编码。老师从"字符串如何以二进制形式存到硬盘上"这一问题出发,先回顾 ASCII 码表及其只能表示英文的局限,然后厘清两个核心概念:字符集(字符到数字的一一映射)与**字符编码**(数字以什么规则存成二进制、如何节省空间)。课程介绍了 Unicode 字符集与 UTF-8 / UTF-16、GBK 等编码方式的关系,演示了 VS Code 中切换编码保存文件的效果与乱码成因,最后讲解了 Python 中 encode() / decode() 的用法,并强调编程时应一律使用 UTF-8。

知识点详解

问题的提出(约 00:00 - 00:45)

  • 文本文件读写的是人类可读的字符串,但存到硬盘/内存里都是二进制——怎么把字符串转化成二进制?这就是字符集和字符编码要解决的问题。

ASCII 及其局限(约 00:45 - 01:25)

  • 第一直觉是 ASCII 码表:把字符翻译成整数(如空格 → 32、a → 97),整数再转成十六进制/二进制存储。
  • ASCII 含扩展共 256 个符号,正好一个字节(2 的 8 次方)能表示的范围。
  • 英文及英文标点都能用 ASCII 转换,非常简单;但中文、韩文、日文、藏文、法语、德语等都找不到对应数字,ASCII 只能转英文。

字符集(约 01:25 - 01:56)

  • 解决思路:给每个符号重新分配一个序号。比如英文到 256,中文接着编号(举例:某字编为 258、另一字编为 1999——强调只是示意,不是真实值)。
  • 字符集:规定字符到数字的一一映射关系。汉字数量有限,哪怕几万个字,就从 0 编号到几万。逻辑非常简单。

字符编码(约 01:56 - 15:10)

  • 有了字符集(数字)之后,保存时还面临"数字怎么存成二进制"的问题:
  • 0255(0x000xFF)一个字节够;258 一个字节存不下,要两个字节(0x0102);1999 更大(0x07CF)。
  • 边界问题(乱码成因之一):如果把"A + 你好"的字节序列直接写入文件,读的人看到的只是一串二进制,不知道先读 1 个字节还是 2 个字节为一个字符——组合错了就**乱码**。
  • 定长方案的缺陷:让所有字符都占 4 字节(2 的 32 次方,足够装下全世界的符号),读取绝不出错,但表示 A 本来 1 字节就够,现在要填 3 个 0,效率超级低。
  • 字符编码:在字符到数字的映射之上,设计一套规则——根据开头字节的状态判断后面跟几个字节表示一个字符——以尽可能缩短同样内容占用的空间(既省磁盘,也省网络传输带宽,同样带宽能传更多字符)。
  • 字符集与编码的关系:不是一一对应,可能一对多,也可能一对一:
  • Unicode 字符集对应多种编码:UTF-8(8 比特即 1 字节为最小字符单位,A 与 ASCII 一样只占 1 字节,大编号的字符按特定规则处理)、UTF-16(每个字符最少占 16 比特即 2 字节);
  • GBK:中文字符集,其编码方式也叫 GBK(一对一),规定了一个字符占 1 字节还是 2 字节;
  • 国标系列:GBK、GB2312、GB18030。
  • 延伸阅读:知乎上有相关问题的高赞回答,例子更细致有趣,建议课后自行阅读。

VS Code 中的编码演示(约 16:10 - 20:25)

  • 回顾第一节课讲过的 VS Code 右下角"选择编码":文本文件保存时都要用特定编码。
  • 默认用 UTF-8,国际通行,汉字、韩语、日语等都包含,推广得最好。
  • 演示:"你好"用 UTF-8 保存,占 6 个字节,十六进制为 E4BDA0 E5A5BD;通过"通过编码保存"换成 GBK 后,十六进制内容完全变了。
  • 乱码成因:文件用 UTF-8 保存却用 GBK 打开(或反之),必然显示不正常。所以别人看着正常你看是乱码时,要想办法弄清文件是用什么编码保存的,再用正确的编码方式重新打开(VS Code 可"通过编码重新打开"逐个猜)。
  • 重要建议:编程时**一律使用 UTF-8** 保存代码和相关数据——它已是事实上的国际通行标准,网上社区交流绝大多数代码也用 UTF-8,否则会遇到各种复杂问题。

Python 中的 encode 与 decode(约 21:13 - 22:45)

  • bytes(二进制)与 str(字符串)之间的转换:
  • 字符串 → 二进制:'你好'.encode(),默认用 UTF-8;指定编码则传入编码名,如 .encode('gbk');
  • 二进制 → 字符串:a.decode(),否则 print 出来的是一堆看不懂的二进制(bytes 字面值)。
  • 老师自述上节演示时曾忘记 decode 要指定/匹配编码,正是此处知识。

答疑:如何判断未知文本的编码(约 22:45 - 23:46)

  • 在没有任何额外信息的情况下,基本判断不了;只有一些统计工具能根据数学统计方法猜测二进制文件最可能用了哪种编码,多数情况下也只能靠猜。
  • 所以才需要一个公认的标准——大家都用 UTF-8 就没有这个问题了。这是困扰计算机界很久的问题;Python 里有相应的检测函数,但不是本课重点,感兴趣可自行查阅。

示例与演示

  1. 白板推演:从 ASCII(256 符号、一字节)讲到给中文编号的字符集思想,再推演变长存储的边界问题与定长 4 字节的浪费,引出编码设计动机。
  2. VS Code 实操:同一文本"你好",分别以 UTF-8(E4BDA0E5A5BD,6 字节)和 GBK 保存,用十六进制工具查看两者字节完全不同。
  3. 乱码演示:GBK 保存的"你好"用 UTF-8 打开显示乱码,再用 GBK 重新打开恢复正常。
  4. Python 演示:'你好'.encode() / .encode('gbk') 得到 bytes;对 bytes 调用 decode() 还原字符串。

重点与难点

  • 区分两个概念:字符集 = 字符 ↔ 数字的映射;字符编码 = 数字 → 二进制的存储规则(变长、省空间)。Unicode 是字符集,UTF-8/UTF-16 是它的不同编码实现;GBK 则字符集与编码同名。
  • 乱码的本质:写入和读取用了不同的编码方式,或无法确定字符边界。
  • UTF-8 兼容 ASCII(英文字符仍占 1 字节),是国际通行标准,编程一律用 UTF-8。
  • encode(str → bytes)与 decode(bytes → str)方向不能搞反,编码名要匹配。
  • 无法可靠地自动判断一个文本文件的编码,只能靠统计猜测——遵守统一标准才是根本解法。

关联内容

  • 承接 8.1 节字面值中"可打印 ASCII 字符"的显示规则,以及 8.2 节解析 BMP 时 decode() 的伏笔,本节把字符 ↔ 二进制的转换讲透。
  • 呼应第 1 章(第一节课)介绍的 VS Code 右下角编码选择功能;与早期学过的 ord/chr(字符与 ASCII 数字互转)一脉相承。
  • 文本文件读写(第 7 章)背后的编码问题在本节得到解释;乱码处理是后续文件操作、网络传输的通用前置知识。