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