title: 15.python的字符编码
date: 2020-11-02 15:36:01
categories:
- 程序语言
- Python
- python基础
字符串类型、文本文件的内容都是由字符组成的,但凡涉及到字符的存取,都需要考虑字符编码的问题。
字符编码这个知识点的典型特征就是理论多、结论少,但对于开发而言只需要记住结论即可,下面让我们来一点点介绍它
前要知识
三大核心硬件
所有软件都是运行硬件之上的,与运行软件相关的三大核心硬件为cpu、内存、硬盘
- 软件运行前,软件的代码及其相关数据都是存放于硬盘中的
- 任何软件的启动都是将数据从硬盘中读入内存,然后cpu从内存中取出指令并执行
- 软件运行过程中产生的数据最先都是存放于内存中的,若想永久保存软件产生的数据,则需要将数据由内存写入硬盘
文本编辑器读取文件内容的流程
-
启动一个文件编辑器(文本编辑器如nodepad++,pycharm,word)
-
文件编辑器会将文件内容从硬盘读入内存
-
文本编辑器会将刚刚读入内存中的内容显示到屏幕上
python解释器执行文件的流程
python源代码存放的也是文本,所以也是文本文件,过程与文本编辑器类似,以python test.py为例,执行流程如下
-
启动python解释器,此时就相当于启动了一个文本编辑器
-
python解释器相当于文本编辑器,从硬盘上将test.py的内容读入到内存中
-
python解释器解释执行刚刚读入的内存的内容,开始识别python语法
总结
python解释器与文件本编辑的异同如下
相同点
前两个阶段二者完全一致,都是将硬盘中文件的内容读入内存,详解如下
python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样
不同点
在阶段3时,针对内存中读入的内容处理方式不同,详解如下
文本编辑器将文件内容读入内存后,是为了显示或者编辑,根本不去理会python的语法,而python解释器将文件内容读入内存后,可不是为了给你瞅一眼python代码写的啥,而是为了执行python代码、会识别python语法)
与字符编码相关
python在前两个阶段,将文本文件导入到python解释器之前保证不乱码,以及在第三阶段识别变量语法的时候保证不乱码
字符编码介绍
现在人与计算机交互的时候,用的都是人类能够读懂的字符,而其实计算机本身只能识别二进制数
二进制数即由0和1组成的数字,例如010010101010。计算机是基于电工作的,电的特性即高低电平,人类从逻辑层面将高电平对应为数字1,低电平对应为数字0,这直接决定了计算机可以识别的是由0和1组成的数字
字符编码
由于计算机只能识别二进制数,所以人类的字符到计算机中的二进制数需要经历一个翻译的过程
翻译过程必须参照一个特定的标准,这个标准被称为字符编码表,该表存放的就是字符与数字的一一对应关系,而这个翻译现在使用更加专业的名词,就被称为编码
字符编码表发展历史
阶段一 :美国一家独大
现代计算机起源于美国,所以最先考虑仅仅是让计算机识别英文字符,于是诞生了ASCII表
# ASCII表的特点:
1、采用ASCII码采用8位二进制数对应一个英文字符,且只有英文字符与数字的一一对应关系
2、一个英文字符对应1Bytes,1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
阶段二:诸侯割据、天下大乱
由于起初计算机只认识英文,给国人的使用带来了很多的不便,所以为了让计算机能够识别中文和英文,中国人定制了GBK
# GBK表的特点:
1、由于中文字符较多,GBK选择使用16位二进制数存储,但只有中文字符、英文字符与数字的一一对应关系
2、一个英文字符对应1Bytes
一个中文字符对应2Bytes
补充说明:
1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符
每个国家都各自的字符,为让计算机能够识别自己国家的字符外加英文字符,各个国家都制定了自己的字符编码表
# Shift_JIS表的特点:
1、只有日文字符、英文字符与数字的一一对应关系
# Euc-kr表的特点:
1、只有韩文字符、英文字符与数字的一一对应关系
此时,美国人用的计算机里使用字符编码标准是ASCII、中国人用的计算机里使用字符编码标准是GBK、日本人用的计算机里使用字符编码标准是Shift_JIS,如下图所示,
字符编码发展到了这个阶段,可以用一句话概括:诸侯割据、天下大乱
阶段三:分久必合
阶段二出现了一个问题,每个地方的计算机系统,就存在了地域间隔的情况,不利于跨国之间的文件传输,于是乎1990年开始研发,1994年正式公布的万国牌编码规则unicode
就成为了解决方案
#1. 存在所有语言中的所有字符与数字的一一对应关系,即兼容万国字符
#2. 采用16位二进制数(2Bytes)进行对应,个别生僻字采用4Bytes、8Bytes
现在还有很多的老文件并不支持unicode编码,unicode编码还支持与其他编码方式进行转换
# 文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的,存放于硬盘中,则可以转换成任意其他编码,只要该编码可以支持相应的字符
英文字符--->unciode格式的数字--->ASCII格式的数字
中文字符、英文字符--->unicode格式的数字--->gbk格式的数字
日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字
# 老的编码都可以转为unicode,但是不能互相转(例如:因为gbk格式的汉字并不出现在ASCII格式中,但他们都支持英文,所以可以英文转换)
由字符转换成内存中的unicode,以及由unicode转换成其他编码的过程,都称为编码encode
由内存中的unicode转换成字符,以及由其他编码转换成unicode的过程,都称为解码decode
UTF-8的由来
理论上是可以将内存中unicode格式的二进制直接存放于硬盘中的,但由于unicode固定使用两个字节来存储一个字符,如果多国字符中包含大量的英文字符时,使用unicode格式存放会额外占用一倍空间(英文字符其实只需要用一个字节存放即可),然而空间占用并不是最致命的问题,最致命地是当我们由内存写入硬盘时会额外耗费一倍的时间,所以将内存中的unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式,这种格式即utf-8(全称Unicode Transformation Format,即unicode的转换格式)
多国字符—√—》内存(unicode格式的二进制)——√—》硬盘(utf-8格式的二进制)
那为何在内存中不直接使用utf-8呢?
utf-8是针对Unicode的可变长度字符编码:一个英文字符占1Bytes,一个中文字符占3Bytes,生僻字用更多的Bytes存储
unicode更像是一个过渡版本,因为它还需要兼容老编码格式的软件,而我们新开发的软件或文件存入硬盘都采用utf-8格式,等过去几十年,所有老编码的文件都淘汰掉之后,会出现一个令人开心的场景,即硬盘里放的都是utf-8格式,此时unicode便可以退出历史舞台,内存里也改用utf-8,天下重新归于统一
字符编码
内存中固定使用unicode无论输入任何字符都不会发生乱码,我们可以改变的只是存入硬盘时的编码方式,如果编码设置不正确会出现乱码
存乱了:存乱了会导致数据丢失,没有解决办法
取乱了:取乱了,只需要将编辑器切换为对应的编码格式即可
文件头指定编码格式 - 只能控制读取 不能控制编译和写入
Python3默认读文件为UTF-8,而Python2默认读文件为ASCII码
在python文件的首行写上
# coding:gbk(当初文件写入硬盘时采用的编码格式)
解释器会先用默认的编码方式读取文件的首行内容,由于首行是纯英文组成,而任何编码方式都可以识别英文字符
注意:这里的头文件是告诉编辑器采用什么方式读取,而不是存储,存储还是按照编辑器设置的编码格式存
python2的str类型unicode编码格式
Python3的str类型默认直接存成unicode格式,无论如何都不会乱码,但是,python早于unicode格式诞生,所以python2并不会直接把字符串存成unicode格式,会根据平台默认格式来存储,所以可能导致乱码
# 在字符串前面加个u将字符串存成unicode
x = u'存放为unicode编码'
python2的解释器在进行解码操作的时候都是以自己为标准的,例如pycharm设置的时候默认使用utf-8,而win平台,在进行解码的时候就会默认使用gbk编码
PS:文件头只能控制读取,不能控制字符串编译格式
# coding:utf-8
x = '上'
print(x)
我们将这段代码放入python2中运行,在pycharm中可以成功运行,但是放入windows平台的cmd中,由于会被认为是gbk编码就会导致乱码
为了正常编译就需要加’u’
# coding:utf-8
x = u'上'
print(x)
编码解码
内存中默认为Unicode,我们人为将Unicode转为gbk,目的是为了支持将编码往其他的平台存放
# coding:utf-8
x = '上'
# 编码
res = x.encode('gbk') # Unicode -> gbk
print(res) # b'\xc9\xcf'
print(type(res)) # <class 'bytes'>
# 解码 - 用什么编码就应该用什么解码
print(res.decode('gbk'))
补充知识
GBK编码方式
已知,计算机识别的是二进制,GBK编码中,英文占用1个Byte位,中文占用两个
案例 a上b 占用比特位模拟(图中二进制用于模拟原理,并不是真实二进制数)
现在问题就来了,怎么区分二进制值是字母还是汉字?
关键在于进制首位的那个0和1,是0就为只读当前比特,1就代表着继续往后读,如图所示:英文占用1个比特位所以首位为0,而中文占用了两个,所以首位从第二个比特位1,读到了第三个比特位1,发现第四个比特位不是1之后,截至,读取是个中文
pycharm的文件头设置
文件头只需要在主文件加载文件头,并不需要把后续分散文件加上文件头,python在主文件中加了文件头,就会全部应用,所以并不推荐进行设置(除非以后都写单文件程序)
文件头设置方式
#!/usr/bin/env python3.8
告诉linux系统采用解释器的位置
使用前运行代码
python3.8 xx.py
使用后运行代码
./xx.py
第二种设置方式
#!/user/bin/env python3.8
让env在系统变量中查找python3.8,然后达到运行./xx.py的目的
# -- coding utf-8 --
效果与#coding:utf-8
相同,这么写只是为了美观
文件注释
"""
@version:3.8
@author:kinght
@file:${NAME}.py
@time:${DATE} ${TIME}
"""
3.8 xx.py
使用后运行代码
./xx.py
第二种设置方式
#!/user/bin/env python3.8
让env在系统变量中查找python3.8,然后达到运行./xx.py的目的
### # -*- coding utf-8 -*-
效果与`#coding:utf-8`相同,这么写只是为了美观
### 文件注释
```python
"""
@version:3.8
@author:kinght
@file:${NAME}.py
@time:${DATE} ${TIME}
"""
注释上解释器版本,开发者,文件名,文件创建时间