字符编码
一切都用数字来表示
我们眼睛看到的电脑上显示的字,包括汉字、英文、日文、韩文,这些文字,在计算机内部是怎么存储的?
难道是刻上去的? 比如刻在硬盘上一个个这样的字?
当然不是,计算机都是用数字存储信息的。
用 磁场、电信号 等物理介质的 正负 状态 表示 0 和 1,如果一个数字只由0和1 表示,就是一个2进制的数字表示法。
用连续的0和1,就可以代表很大的数字。
比如
二进制 的 10 就表示十进制 的 2
二进制 的 110 就表示十进制 的 6
从右向左,每位 上的 1 分别代表 2的0次方(就是1), 2的1次方(就是2),2的2次方(就是4),2的3次方(就是8) 等等, 依次类推。
根据不同的使用场合,数字可以表示不同的意义。
比如,需要存储 颜色 信息的时候, 0 可以用来代表 白色, 1 代表红色, 2 代表 黑色等等。
如果有10000种颜色,可以用 0 到 9999 一共 10000个数字分别代表他们。
同样的, 我们要存储 文字 信息,也可以用不同的数字来代表不同的文字。
字符集
计算机是美国人发明的,所以在开始的时候, 他们也没有想到将来要支持全世界的文字。
所以,最早的时候,他们定义了一个规范,定义一些数字 用来代表美国人使用的文字符号。
而美国人的单词都是用字母拼出来的, 所以他们常用的基础文字符号比较少。
就是字母,再加上 !@#$%^&*(){[]}+- 等等这些符号,也就100多个。
他们定义的规范就叫 ASCII (American Standard Code for Information Interchange) ,中文大意就是 美国信息交换标准码
大家可以参考 百度上面的ASCII表
这个 ASCII 码里面用128个数字代表了128个字符。
要存储和传输这 128个字符对应的文字信息 也很简单, 二进制有 8 位就足够了。
因为2的8次方等于256,可以存储 从 0 到255 一共 256个数字, 可以表示 最多 256 个文字符号。
二进制有 8 位 的长度 被称为 一个 字节 。
所以一个字节就可以存放 任何一个ASCII 文字符号。 一个字节(8位)足以存储任何一个ASCII字符,这在当时非常高效。
ASCII 这样的 用数字代表文字符号的规范,就被称之为 字符集
后来计算机传遍了全世界,其它国家的文字符号就多了。
我们中文,就有这么多的文字符号。小学的时候,我们有个3000常用字表, 光常用字就有3千个。
当然还有 韩文、日文、繁体中文、阿拉伯文等等。
显然ASCII是不够的,我们需要其它的数字来代表这些文字符号。
开始各个国家和地区 自己定义了自己的 字符集。
比如我们中国就定义另外一套规范, 规定了 用什么样的数字代表什么样的文字符号。
- 中国:制定了 GB2312、GBK、GB18030 等一系列字符集。
- 日本:有 Shift_JIS。
- 台湾:有 Big5。
各个国家定义了各国的文字的字符集,这就带来了一个问题。
现在全球交流很密切,有时一篇文章会使用多国文字,比如 同时使用 日文和中文。
而各国的字符集往往是不包含别国文字的,比如 GBK就不会包含 日文。
这时就没法在一篇文章中存储多国文字了。
为了解决这个问题。 国际标准化组织定义了一个字符集,想包括世界上所有的文字符号。
这就是大名鼎鼎的 unicode 字符集。
这个字符集里面包括了现今世界上的常用文字符号 和 其对应的数字表示。
这样就解决了在一篇文章中包含多国文字的问题了。
字符编码
unicode 出现了,很好,但是还有一个问题。 字符怎么存储和传输。
由于历史原因,计算机是 以字节为单位 来 存储和传输数据的,一个字节 由 8位二进制数字表示,最多可以表示从0到255, 一共256个字符。
最初只有ASCII码的时候,一个字节 就可以存储传输任何文字。
但是unicode里面有10多万的文字符号,数字范围 远远 超过 一个字节所能代表的数字。
所以一个字节不够。
那么怎么用多个字节来 表示这些数字呢?
这就有需要另外的规范。这些 如何用字节表示 字符对应的数字 就是 字符编码 规范
unicode字符 的编码,最常用的规范是 UTF8 和 UTF16 。
比如 UTF8 表示 中文字符 你 ,就是用 3个字节表示的,对应的16进制表示是 E4 、BD、 A0
当然我们中文字符集gb系列使用另外的编码规范。
以后,我们会写一篇文章,以UTF8为例,讲解它是如何用多个字节存储 数字的。
这里我们只要知道 不同的 编码规范 对数字有不同的 用字节表示的方法就行了。
有了编码规范,字符集 中的数字就可以转化为字节进行存储和传输了。下面是学习视频
Python3 的字符编码和解码
字符串编码
我们Python3语言里面的字符串对象是unicode字符串。
但是,字符串在 内存中的表示 和 存储/传输时的表示 是两回事。为了高效地存储和通过网络传输,我们需要将字符串 编码 为字节序列。
Python(以及C#, Java等很多语言)在内存中通常使用UTF-16或其变种来表示字符串,这方便进行字符的快速索引和操作。
然而,当我们需要把字符串写入文件或通过网络发送时,通常不会直接使用UTF-16编码,而是更倾向于使用 UTF-8 编码。为什么呢?
主要原因是 空间效率和兼容性。
-
对于英文为主的内容:
- UTF-8 编码一个英文字符(ASCII字符)只占用 1个字节。
- UTF-16 编码一个英文字符(ASCII字符)至少占用 2个字节。
因此,如果文本内容主要是英文、数字和常见符号(比如网页HTML代码、JSON数据、源代码文件),使用UTF-8编码会比UTF-16节省将近一半的存储空间。
-
对于中文为主的内容:
- UTF-8 编码一个常用汉字占用 3个字节。
- UTF-16 编码一个常用汉字占用 2个字节。
在这种情况下,UTF-16看起来更节省空间。但即便如此,UTF-8仍然是首选,因为它已经成为互联网的事实标准。几乎所有的网络协议、文件格式和API都默认或强制要求使用UTF-8编码。它的另一个巨大优势是完全向后兼容ASCII,一个只认识ASCII的旧系统也能安全地处理UTF-8编码的文本(它会忽略多字节字符,但不会破坏数据流)。
综上所述,为了节省空间和保证最佳的兼容性,Python语言要对字符串对象进行存储和传输的时候,通常要使用字符串的 encode 方法,参数指定为 'utf8',将其编码为一个 bytes 对象。
bytes对象的底层就是用一个个的字节来存储字符串中的文字的。
同样的字符串,用不同的编码方式,有时会产生不同的bytes结果。
比如
print ('你好'.encode('utf8')) # 输出 b'\xe4\xbd\xa0\xe5\xa5\xbd'
print ('你好'.encode('gbk')) # 输出 b'\xc4\xe3\xba\xc3'
输出内容 中 b 开头,表示这是一个 字节串bytes 对象
\x 说明是用16进制表示一个字节
你好 两个字,使用 utf8 编码 后的字节串,用16进制来表示 就是6个字节 e4bda0 e5a5bd
e4bda0 对应 你
e5a5bd 对应 好
你好 两个字,使用 gbk 编码 后的字节串,用16进制来表示 却是4个字节 c4e3 bac3
c4e3 对应 你
bac3 对应 好
encode方法返回的是编码后的字节串对象bytes 编码为字节串对象 bytes 就可存储到文件或者传输到网络中去了。 我们后面的学习会讲如何进行文件存储和网络传输
字节串解码
当我们的Python程序从文件中读入文字信息, 从网络上接收 文字信息,获取的数据通常是使用某种字符编码后的 字节串。
程序通常需要解码这些 字节串 为 字符串 ,这样才方便程序 理解和处理 字符信息。
Python语言的解码 都是解码成 unicode字符串对象。
要解码字节串,必须要知道这个字节串是用什么字符编码的方式进行编码的。
如果知道了,就可以用字节串对象的decode方法 进行解码,参数指定了编码方式
比如
print(b'\xe4\xbd\xa0\xe5\xa5\xbd'.decode('utf8'))
print(b'\xc4\xe3\xba\xc3'.decode('gbk'))
上面的两行代码都可以解码出 字符串 '你好'
如果不小心写错了参数指定的编码方式,就可能返回错误解码结果 , 比如
print(b'\xe4\xbd\xa0\xe5\xa5\xbd'.decode('gbk'))
返回 浣犲ソ
或者解码失败报错,比如
print(b'\xc4\xe3\xba\xc3'.decode('utf8'))
报如下错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte
一些字符编解码技巧
unicode数字转换为字符
把 unicode数字转换为字符, 使用函数 chr() , 比如:
>>> chr(50)
'2'
>>> chr(20013)
'中'
>>> chr(0x4e2d) # 0x开头表示数字是16进制
'中'
字符转换为unicode数字
反过来,要把 字符转换为对应的unicode数字,使用函数 ord()
该函数参数字符串里面只能有一个字符
>>> ord('2')
50
>>> ord('中')
20013
字符串编码为 unicode转义数字
除了utf8,gbk 还有一种常见的编码方式,叫做 unicode-escape ,就是直接用unicode数字字符串表示字符,如下所示
>>> '白月黑羽'.encode('unicode-escape')
b'\\u767d\\u6708\\u9ed1\\u7fbd'
用unicode转义数字 写字符串
>>> '\u767d\u6708\u9ed1\u7fbd'
'白月黑羽'
直接用16进制数字创建 bytes
>>> b'\xb0\xd7\xd4\xc2\xba\xda\xd3\xf0'
b'\xb0\xd7\xd4\xc2\xba\xda\xd3\xf0'
>>> b'\xb0\xd7\xd4\xc2\xba\xda\xd3\xf0'.decode('gbk')
'白月黑羽'
字节串 和 16进制表示字节的字符串
>>> a = b'hello,123'
>>> a.hex()
'68656c6c6f2c313233'
反向操作,把 16进制表示字节的字符串 转化为 字节串就是
>>> bytes.fromhex('68656c6c6f2c313233')
b'hello,123'