1.引入
字符串类型、文本文件的内容都是由字符组成的,但凡涉及到字符的存取,都需要考虑字符
编码的问题。字符编码这个知识点的典型特征就是理论多,结论少,但对于开发者而言只需要记住结论即可,下面进入该知识点的详细介绍。
2.知识储备
2.1 三大核心硬件
所有软件都是运行在硬件之上的,与运行软件相关的三大核心硬件为:CPU、内存、硬盘,我们需要明确三点:
- 软件运行前,软件的代码及其相关数据都是存放于硬盘中的
- 任何软件的启动都是将数据从硬盘中读入内存,然后CPU从内存中取出指令并执行
- 软件运行过程中产生的数据最先都是存放于内存中的,若想永久保存软件产生的数据,则需要将数据由内存写入硬盘
2.2 文本编辑器读取文件内容的流程
- 阶段1:启动一个文件编辑器(文本编辑器如Typora,PyCharm,Word)
- 阶段2:文件编辑器会将文件内容从硬盘读入内存
- 阶段3:文本编辑器会将刚刚读入内存中的内容显示到屏幕上
2.3 Python解释器执行文件的流程
以python test.py为例,执行流程如下:
- 阶段1:启动python解释器,此时就相当于启动了一个文本编辑器
- 阶段2:python解释器相当于文本编辑器,从硬盘上将test.py的内容读入到内存中
- 阶段3:python解释器解释执行刚刚读入的内存的内容,开始识别python语法
2.4 总结
python解释器与文本编辑的异同如下:
-
相同点:前两个阶段二者完全一致,都是将硬盘中文件的内容读入内存,详解如下:
python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样。
-
不同点:在阶段3时,针对内存中读入的内容处理方式不同,详解如下:
文本编辑器将文件内容读入内存后,是为了显示或者编辑,根本不去理会python的语法,而python解释器将文件内容读入内存后,可不是为了给你瞅一眼python代码写的是啥,而是为了执行python代码、会识别python语法。
3.字符编码
3.1 什么是字符编码?
人类在与计算机交互时,用的都是人类能读懂的字符,如中文字符、英文字符、日文字符等
而计算机只能识别二进制数。
二进制数即由0和1组成的数字,例如010010101010。计算机是基于电工作的,电的特性即高低电平,人类从逻辑层面将高电平对应为数字1,低电平对应为数字0,这直接决定了计算机可以识别的是由0和1组成的数字。
毫无疑问,由人类的字符到计算机中的数字,必须经历一个过程,如下:
翻译的过程必须参照一个特定的标准,该标准称之为字符编码表,该表上存放的就是字符与数字一一对应的关系。
字符编码中的编码指的是翻译或者转换的意思,即将人能理解的字符翻译成计算机能识别的数字。
3.2 字符编码的发展史
字符编码的发展经历了三个重要的阶段,如下:
3.2.1 阶段一:一家独大
现代计算机起源于美国,所以最先考虑仅仅是让计算机识别英文字符,于是诞生了ASCII表,ASCII表的特点:
- 只有英文字符与数字的一一对应关系
- 一个英文字符对应1Bytes,1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
3.2.2 阶段二:诸侯割据、天下大乱
为了让计算机能够识别中文和英文,中国人定制了GBK,GBK的特点:
-
只有中文字符、英文字符与数字的一一对应关系
-
一个英文字符对应1Bytes
一个中文字符对应2Bytes
补充说明:2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示几乎所有中文字符
每个国家都有各自的字符,为让计算机能够识别自己国家的字符外加英文字符,各个国家都制定了自己的字符编码表:
-
Shift_JIS表(日本)的特点:只有日文字符、英文字符与数字的一一对应关系
-
Euc-kr表(韩国)的特点:只有韩文字符、英文字符与数字的一一对应关系
此时,美国人用的计算机里使用字符编码标准是ASCII,中国人用的计算机里使用字符编码标准是GBK,日本人用的计算机里使用字符编码标准是Shift_JIS,如下图所示:
字符编码发展到了这个阶段,可以用一句话概括:诸侯割据、天下大乱,详解如下:
-
图1中,文本编辑存取文件的原理如下:
- 文本文件内容全都为字符,无论存取都是涉及到字符编码问题
- 1、存文本文件:人类通过文本编辑器输入的字符会被转化成ASCII格式的二进制存放于内存中,如果需要永久保存,则直接将内存中的ASCII格式的二进制写入硬盘。
- 2、读文本文件: 直接将硬盘中的ASCII格式的二进制读入内存,然后通过ASCII表反解成英文字符。
-
图2图3都是相同的过程,此时无论是存还是取由于采用的字符编码表一样,所以肯定不会出现乱码问题,但问题是在美国人用的计算机里只能输入英文字符,而在中国人用的计算机里只能输入中文字符和英文字符..….
毫无疑问人们希望计算机允许输入万国字符并均可识别、不乱码,而现阶段计算机采用的字符编码ASCII、GBK、Shift_JIS都无法识别万国字符,所以人们必须定制一个兼容万国字符的编码表,请看阶段三。
3.2.3 阶段三:分久必合,天下统一
Unicode于1990年开始研发,1994年正式公布,具备两大特点:
- 存在所有语言中的所有字符与数字的一一对应关系,即兼容万国字符
- 与传统的字符编码的二进制数都有对应关系,详解如下:
很多地方或老的系统、应用软件仍会采用各种各样传统的编码,这是历史遗留问题。此处需要强调:软件是存放于硬盘的,而运行软件是要将软件加载到内存的,面对硬盘中存放的各种传统编码的软件,想让我们的计算机能够将它们全都正常运行而不出现乱码,内存中必须有一种兼容万国的编码,并且该编码需要与其他编码有相对应的映射/转换关系,这就是Unicode的第二大特点产生的缘由。
文本编辑器输入任何字符都是最先存在于内存中,是Unicode编码的,存放于硬盘中时,则可以转换成任意其他编码,只要该编码可以支持相应的字符
-
英文字符可以被ASCII识别
英文字符--->Unciode格式的数字--->ASCII格式的数字
-
中文字符、英文字符可以被GBK识别
中文字符、英文字符--->Unicode格式的数字--->GBK格式的数字
-
日文字符、英文字符可以被Shift-JIS识别
日文字符、英文字符--->Unicode格式的数字--->Shift-JIS格式的数字
3.3 编码与解码
由字符转换成内存中的Unicode,以及由Unicode转换成其他编码的过程,都称为编码encode。
由内存中的Unicode转换成字符,以及由其他编码转换成Unicode的过程,都称为解码decode。
在诸多文件类型中,只有文本文件的内存是由字符组成的,因而文本文件的存取也涉及到字符编码的问题
3.4 utf-8的由来
注意:如果保存到硬盘的是GBK格式二进制,当初用户输入的字符只能是中文或英文,同理如果保存到硬盘的是Shift_JIS格式二进制,当初用户输入的字符只能是日文或英文……如果我们输入的字符中包含多国字符,那么该如何处理?
多国字符—√—》内存( Unicode格式的二进制)——X—》硬盘(GBK格式的二进制)
多国字符—√—》内存(Unicode格式的二进制)——X—》硬盘(Shift_JIS格式的二进制)
多国字符—√—》内存(Unicode格式的二进制)——√—》硬盘(???格式的二进制)
理论上是可以将内存中Unicode格式的二进制直接存放于硬盘中的,但由于Unicode固定使用两个字节来存储一个字符,如果多国字符中包含大量的英文字符时,使用Unicode格式存放会额外占用一倍空间(英文字符其实只需要用一个字节存放即可),然而空间占用并不是最致命的问题,最致命的是当我们由内存写入硬盘时会额外耗费一倍的时间,所以将内存中的Unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式,这种格式即utf-8(全称Unicode Transformation Format,即Unicode的转换格式)
多国字符—√—》内存(Unicode格式的二进制)——√—》硬盘(utf-8格式的二进制)
那为何在内存中不直接使用utf-8呢?
utf-8是针对Unicode的可变长度字符编码,一个英文字符占1Bytes,一个中文字符占3Bytes,生僻字用更多的Bytes存储.
也就意味着如果用户输入的字符是:你y好,在内存中需要先经历计算的过程:“你”应该用3Bytes,“y”应该用1Bytes,“好”应该用3Bytes,然后才能存储,所以内存中如果直接使用utf-8格式去存储字符,耗费的总时间=计算时间+存储时间,而内存中使用定长的Unicode格式存储字符,就省去了计算时间,所以内存中使用Unicode来存储字符会浪费空间,但是会提升速度,这是一种用空间换时间的方法.
Unicode更像是一个过渡版本,我们新开发的软件或文件存入硬盘都采用utf-8格式,等过l了几十年后,所有老编码的文件都淘汰掉之后,会出现一个令人开心的场景,即硬盘里放的都是utf-8格式,此时Unicode便可以退出历史舞台,内存里也改用utf-8,天下重新归于统一。
4.字符编码的应用
我们学习字符编码就是为了存取字符时不发生乱码问题:
- 内存中固定使用Unicode无论输入任何字符都不会发生乱码
- 我们能够修改的是存/取硬盘的编码方式,如果编码设置不正确将会出现乱码问题。乱码问题分为两种:
- 存乱了:如果用户输入的内容中包含中文和日文字符,如果单纯以Shift_JIS存,日文可以正常写入硬盘,而由于中文字符在Shift_jis中没有找到对应关系而导致存乱了
- 读乱了:如果硬盘中的数据是Shift_JIS格式存储的,采用GBK格式读入内存就读乱了
总结:
- 保证存的时候不乱:在由内存写入硬盘时,必须将编码格式设置为支持所输入字符的编码格式
- 保证读的时候不乱:在由硬盘读入内存时,必须采用与写入硬盘时相同的编码格式
4.1 文本编辑器notepad++存取文本文件
文本编辑器存取的都是文本文件,而文本文件中包含的内容全为字符,所以存取文本文件都涉及到字符编码的问题。
4.2 Python解释器执行文件的前两个阶段
执行py文件的前两个阶段就是Python解释器读文本文件的过程,与文本编辑器读文本文件的前两个阶段没有任何区别,要保证读不乱码,则必须将Python解释器读文件时采用的编码方式设置为文件当初写入硬盘时的编码格式,如果没有设置,Python解释器则才用默认的编码方式,在Python3中默认为utf-8,而在Python2中默认为ASCII,我们可以通过指定文件头来修改默认的编码。
在文件首行写入包含#号在内的以下内容:
# coding: 当初文件写入硬盘时采用的编码格式
解释器会先用默认的编码方式读取文件的首行内容,由于首行是纯英文组成,而任何编码方式都可以识别英文字符。
4.3 Python解释器执行文件的第三个阶段
设置文件头的作用是保证运行Python程序的前两个阶段不乱码,经过前两个阶段后py文件的内容都会以Unicode格式存放于内存中。
在经历第三个阶段时开始识别Python语法,当遇到特定的语法如:name = '上'(代码本身也都全都是Unicode格式存的)时,需要申请内存空间来存储字符串'上',这就又涉及到应该以什么编码存储‘上’的问题了。
在Python3中,字符串类的值都是使用Unicode格式来存储;
由于Python2的盛行是早于Unicode的,因此在Python2中是按照文件头指定的编码来存储字符串类型的值的(如果文件头中没有指定编码,那么解释器会按照它自己默认的编码方式来存储‘上’),所以,这就有可能导致乱码问题。
# coding:utf-8
x = '上' # x的值为utf-8格式的二进制
print(x) # 打印操作是将x的值,即utf-8格式的二进制交给终端,当终端收到后发现并不是Unicode(只有Unicode才与字符有对应关系),所以终端会执行操作:utf-8二进制---解码-->Unicode格式的二进制,解码的过程终端会采用自己默认的编码,而在PyCharm的终端默认编码为utf-8、Windows下的cmd终端的默认编码为GBK,所以该打印操作在PyCharm中显示正常,而在Windows下的cmd中则乱码
# 在Windows下的cmd中运行效果如下
C:\Users\Administrator>python2 E:\aaa.py
涓
Python2后推出了一种补救措施,就是在字符串类型前加u,则会将字符串类型强制存储为Unicode格式,这就与Python3保持一致了,对于Unicode格式无论丢给任何终端进行打印,都可以直接对应字符不会出现乱码问题。
# coding:utf-8
x = u'上' # 即便文件头为utf-8,x的值依然存成Unicode
4.4 字符串encode编码与解码decode解码的使用
-
编码:将人类能够读懂的字符按照指定的编码格式转换成数字(二进制数据)
-
解码:将数字(二进制数据)按照指定的编码格式转换成人类能够读懂的字符
# 1、Unicode格式------编码encode-------->其它编码格式
>>> x = '上' # 在Python3中'上'被存成unicode
>>> res = x.encode('utf-8')
>>> res, type(res) # 将Unicode编码成了utf-8格式,而编码的结果为bytes类型,可以直接当作二进制去使用
(b'\xe4\xb8\x8a', <class 'bytes'>)
# 2、其它编码格式------解码decode-------->Unicode格式
>>> res.decode('utf-8')
'上'