Skip to content

Commit

Permalink
字符集和字符编码
Browse files Browse the repository at this point in the history
  • Loading branch information
satori1995 committed Dec 14, 2024
1 parent f4158dc commit b5ae2cf
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
91 changes: 91 additions & 0 deletions src/28.字符集和字符编码.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## 楔子

接下来我们将分析 Python 的字符串,这应该是使用频率最高的数据结构了,我们会通过多篇文章来详细阐述字符串的实现原理。

首先字符串是一个变长对象,因为不同长度的字符串所占的内存是不一样的。但同时字符串又是一个不可变对象,因为一旦创建就不可以再修改了。

## 多字节编码

我们知道计算机的存储单位是字节,一个字节可以表示 256 种字符,对于发明计算机的美国人来说足够了。因为英文字母算上大小写只有 52 个,即便再加上一些特殊字符,数量也不会超过 256 个,因此一个字节完全可以表示。

但随着计算机的普及,越来越多的非英文字符出现,导致一个字节已经无法表示了。所以只能曲线救国,对于一个字节无法表示的字符,使用多个字节表示,这便是<font color="blue">多字节编码</font>。而多字节编码也存在相应的问题,就是容易出现乱码。

到这里先不继续往下深入,我们先来理清楚一些概念。

## 字符集和字符编码

估计有很多小伙伴搞不清这两者的区别,我们先来解释一下所谓的字符集和字符编码是怎么一回事?

+ <font color="blue">字符集</font>:系统支持的所有字符组成的集合,像 ASCII、GB2312、Big5、unicode 都属于字符集。只不过不同的字符集所能容纳的字符个数不同,比如 ASCII 字符集不包含中文,unicode 则可以容纳世界上的所有字符;
+ <font color="blue">多字节编码</font>:负责将每个字符转成一个或多个计算机可以接受的具体数字,该数字可以理解为编号,因此字符编码维护了字符和编号之间的对应关系。而编码也分为多种,比如 ASCII、GBK、UTF-8 等等,字符编码不同,那么字符转换之后的编号也不同,当然能转化的字符种类也不同。比如 ASCII 这种字符编码,它就只能转换 ASCII 字符。

当然,ASCII 比较特殊,它既是字符集、也是字符编码。并且不管采用什么编码,ASCII 字符对应的编号永远是相同的。

将字符串中的每一个字符转成对应的编号,那么得到的就是<font color="blue">字节序列(bytes 对象)</font>,因为计算机存储和网络通讯的基本单位都是字节,所以字符串必须以字节序列的形式进行存储或传输。

因此字符串和字节序列在某种程度上是很相似的,字符串按照指定的编码进行 encode 即可得到字节序列,<font color="blue">也就是将每个字符都转成对应的编号</font>;字节序列按照相同的编码 decode 即可得到字符串,<font color="blue">也就是根据编号找到对应的字符</font>。

比如我们写了一段文本,然后在存储的时候必须先进行 encode,也就是将每一个字符都转成一个或多个系统可以接受的数字、即对应的编号之后,才可以进行存储。

~~~Python
name = "古明地觉"

print(name.encode("gbk"))
"""
b'\xb9\xc5\xc3\xf7\xb5\xd8\xbe\xf5'
"""
print(name.encode("utf-8"))
"""
b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89'
"""
~~~

采用不同的编码,得到的字节序列是不同的,比如使用 gbk 编码 encode,那么也必须使用 gbk 编码 decode。否则会因为无法解析而报错,因为字符编码不同,字符对应的编号也不同。

再比如每个国家都有自己的字符编码,你在日本的一台计算机上写好的文件拿到中国的计算机上打开,很有可能出现乱码。因为字符编码不同,字符和编号之间的对应关系也不同,采用不同的字符编码进行解析肯定会出问题。

但我们说,对于 ASCII 字符来说,由于不管采用哪一种编码,它们得到的编号都是固定的。所以编码对于 ASCII 字符来说,没有任何影响。

~~~Python
name = "satori"

print(name.encode("gbk"))
"""
b'satori'
"""
print(name.encode("gbk").decode("utf-8"))
"""
satori
"""

# 但如果是非 ASCII 字符,就不行了
try:
"你好".encode("gbk").decode("utf-8")
except UnicodeDecodeError as e:
print(e)
"""
'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte
"""
~~~

这里再回忆一下 bytes 对象,创建的时候可以采用字面量的方式,比如 <font color="blue">b"abc"</font>,但是 <font color="blue">b"憨"</font> 却不可以。原因就是<font color="blue">憨</font>这个字符不是 ASCII 字符,那么采用不同的字符编码,其对应的编号是不同的。而解释器又不知道我们使用的是哪一种编码,所以不允许这么做,而是需要通过 <font color="blue">"憨".encode()</font> 的方式手动指定字符编码。

但对于 ASCII 字符而言,不管采用哪一种字符编码,得到的编号都是一样的, 所以 Python 针对 ASCII 字符则允许这种做法,比如 <font color="blue">b"abc"</font>。并且我们看到,对于汉字来说,在编码之后会对应多个编号,而每个编号占 1 字节,因此不同的字符所占的大小可能不同。

## 小结

以上就是字符集和字符编码,字符集就是字符组成的集合,不同字符集所能容纳的字符数量是有限的。字符编码是将字符转成对应的编号,比如将一个字符串中的所有字符都转成对应的编号之后,就得到了字节序列。当然和字符集一样,字符编码能转换的字符种类也是有限的,像汉字我们可以使用 GBK 编码、UTF-8 编码,但是不能使用 ASCII 编码。

以上算是理清楚了一些概念,显然过于简单了,主要是为后面的内容做铺垫。那么下一篇文章,我们就从 Python 的角度来分析字符串的存储方式。

----------------

&nbsp;

**欢迎大家关注我的公众号:古明地觉的编程教室。**

![](./images/qrcode_for_gh.jpg)

**如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。**

![](./images/supports.png)
3 changes: 2 additions & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
+ [24. bytes 对象都支持哪些操作,它们是怎么实现的?](24.bytes对象都支持哪些操作,它们是怎么实现的?.md)
+ [25. 通过 bytes 对象的合并,探究缓冲区的奥秘](25.通过bytes对象的合并,探究缓冲区的奥秘.md)
+ [26. 解密 bytes 对象的缓存池](26.解密bytes对象的缓存池.md)
+ [27. 详解 bytearray 对象的底层实现](27.详解bytearray对象的底层实现.md)
+ [27. 详解 bytearray 对象的底层实现](27.详解bytearray对象的底层实现.md)
+ [28. 字符集和字符编码](28.字符集和字符编码.md)

0 comments on commit b5ae2cf

Please sign in to comment.