楔子
接下来我们将分析 Python 的字符串,这应该是使用频率最高的数据结构了,我们会通过多篇文章来详细阐述字符串的实现原理。
首先字符串是一个变长对象,因为不同长度的字符串所占的内存是不一样的。但同时字符串又是一个不可变对象,因为一旦创建就不可以再修改了。
多字节编码
我们知道计算机的存储单位是字节,一个字节可以表示 256 种字符,对于发明计算机的美国人来说足够了。因为英文字母算上大小写只有 52 个,即便再加上一些特殊字符,数量也不会超过 256 个,因此一个字节完全可以表示。
但随着计算机的普及,越来越多的非英文字符出现,导致一个字节已经无法表示了。所以只能曲线救国,对于一个字节无法表示的字符,使用多个字节表示,这便是多字节编码。而多字节编码也存在相应的问题,就是容易出现乱码。
到这里先不继续往下深入,我们先来理清楚一些概念。
字符集和字符编码
估计有很多小伙伴搞不清这两者的区别,我们先来解释一下所谓的字符集和字符编码是怎么一回事?
- 字符集:系统支持的所有字符组成的集合,像 ASCII、GB2312、Big5、unicode 都属于字符集。只不过不同的字符集所能容纳的字符个数不同,比如 ASCII 字符集不包含中文,unicode 则可以容纳世界上的所有字符;
- 多字节编码:负责将每个字符转成一个或多个计算机可以接受的具体数字,该数字可以理解为编号,因此字符编码维护了字符和编号之间的对应关系。而编码也分为多种,比如 ASCII、GBK、UTF-8 等等,字符编码不同,那么字符转换之后的编号也不同,当然能转化的字符种类也不同。比如 ASCII 这种字符编码,它就只能转换 ASCII 字符。
当然,ASCII 比较特殊,它既是字符集、也是字符编码。并且不管采用什么编码,ASCII 字符对应的编号永远是相同的。
将字符串中的每一个字符转成对应的编号,那么得到的就是字节序列(bytes 对象),因为计算机存储和网络通讯的基本单位都是字节,所以字符串必须以字节序列的形式进行存储或传输。
因此字符串和字节序列在某种程度上是很相似的,字符串按照指定的编码进行 encode 即可得到字节序列,也就是将每个字符都转成对应的编号;字节序列按照相同的编码 decode 即可得到字符串,也就是根据编号找到对应的字符。
比如我们写了一段文本,然后在存储的时候必须先进行 encode,也就是将每一个字符都转成一个或多个系统可以接受的数字、即对应的编号之后,才可以进行存储。
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 字符来说,没有任何影响。
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 对象,创建的时候可以采用字面量的方式,比如 b"abc",但是 b"憨" 却不可以。原因就是憨这个字符不是 ASCII 字符,那么采用不同的字符编码,其对应的编号是不同的。而解释器又不知道我们使用的是哪一种编码,所以不允许这么做,而是需要通过 "憨".encode() 的方式手动指定字符编码。
但对于 ASCII 字符而言,不管采用哪一种字符编码,得到的编号都是一样的, 所以 Python 针对 ASCII 字符则允许这种做法,比如 b"abc"。并且我们看到,对于汉字来说,在编码之后会对应多个编号,而每个编号占 1 字节,因此不同的字符所占的大小可能不同。
小结
以上就是字符集和字符编码,字符集就是字符组成的集合,不同字符集所能容纳的字符数量是有限的。字符编码是将字符转成对应的编号,比如将一个字符串中的所有字符都转成对应的编号之后,就得到了字节序列。当然和字符集一样,字符编码能转换的字符种类也是有限的,像汉字我们可以使用 GBK 编码、UTF-8 编码,但是不能使用 ASCII 编码。
以上算是理清楚了一些概念,显然过于简单了,主要是为后面的内容做铺垫。那么下一篇文章,我们就从 Python 的角度来分析字符串的存储方式。
欢迎大家关注我的公众号:古明地觉的编程教室。
如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。