字符编码

谈字符编码,首先谈谈和字符相关的知识

1. 预备知识

1.1 python的字符相关

1.1.1 字符串

我们之前就了解并学习过字符串就是由一串字符组合形成的,毋庸置疑谈字符编码肯定和字符串有关

1.1.2 文本文件

在我们没有接触python之前,你可能就已经接触了word文件,尤其是在学生时代,各种通知,作业都是word

word文件本质上就是文本文件,由很多字符组成,只不过当初的你把它叫做汉字或者英文字母。

1.2 三大核心硬件

首先所有的软件都是运行在硬件之上的,与运行软件相关的三大核心硬件分别为cpu,内存,硬盘。

首先明确:

  1. 软件运行之前,软件的代码以及相关的数据都是保存在硬盘上的

  2. 任何软件的运行都是把数据从硬盘中读入到内存,然后cpu从内存中取出指令执行

  3. 软件运行过程中产生的数据都是先保存在内存中的,如果想要永久保存数据,则需要将数据由内存

    写入到硬盘中

1.2.2 文本编辑器读取文件内容的流程

#阶段1、启动一个文件编辑器(文本编辑器如nodepad++,pycharm,word)

#阶段2、文件编辑器会将文件内容从硬盘读入内存

#阶段3、文本编辑器会将刚刚读入内存中的内容显示到屏幕上

1.2.3 python解释器执行文件的流程

以python test.py为例,执行流程如下

#阶段1、启动python解释器,此时就相当于启动了一个文本编辑器

#阶段2、python解释器相当于文本编辑器,从硬盘上将test.py的内容读入到内存中

#阶段3、python解释器解释执行刚刚读入的内存的内容,开始识别python语法

1.2.4 总结

python解释器与文件本编辑的异同如下

#1、相同点:前两个阶段二者完全一致,都是将硬盘中文件的内容读入内存,详解如下
python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样

#2、不同点:在阶段3时,针对内存中读入的内容处理方式不同,详解如下
文本编辑器将文件内容读入内存后,是为了显示或者编辑,根本不去理会python的语法,而python解释器将文件内容读入内存后,可不是为了给你瞅一眼python代码写的啥,而是为了执行python代码、会识别python语法)

2. 字符编码介绍

2.1 什么是字符编码

当我们在和计算机进行交互时,用的全都是字符,就是我们人类都够认识的字符,比如中文字符,英文字符等

但是我们都知道计算机只认识0101这种二进制数字。

那么我们在打字,编辑文档的时候,一定会经历一个过程就是把我们人类能够认识的字符转化成010101数字

翻译的时候肯定必须参照一个标准,该标准称之为字符编码表,该表上存放的就是字符与数字一 一对应的关系。

即人类字符和数字的对应关系的一张表,就是字符编码表。编码就是按照字符编码表翻译的意思,

2.2 解释一下你看到的字

假如你敲了一个牛叉,计算机会参照字符编码表,把牛叉翻译成0101这种二进制数字存到内存中,然后你的文本

编辑器又从内存中把这串二进制数字从内存中取了出来,按照同一张字符编码表反解成对应的字符,显示出来。

由于这个过程太快了,你根本感知不到。

2.3 字符编码表的发展史

首先计算机是美国人发明的,所以当初设定第一张对照表的是美国人,且只有美国人认识的英文字符。

2.3.1 第一张字符编码表——ASCII对照表

ASCII对照表的特点:

  1. 只有英文字符与数字的一一对应关系
  2. 一个英文字符对应1Bytes,1Bytes=8bit(位),8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符

注意:英文字符不等同英文字母,字符包括:大小写字母,标点符号,数字,空白。

2.3.2 各种国家的编码表

虽然电脑是美国人发明的,但是随着历史的发展,各个国家的人们都开始接触,并想使用电脑。

那么就要提前解决一个问题——就是原本的ASCII对照吧表中没有自己国家语言的字符。

于是各个国家开始在ASCII编码表的基础上编制自己国家语言的字符编码表。

gbk(国编码)

GBK表的特点:

  1. 只有中文字符、英文字符与数字的一一对应关系

  2. 一个英文字符对应1Bytes

    一个中文字符对应2Bytes

补充说明: 1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符

​ 2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符

​ 实际上英文字符+中文字符+韩文字符+日文字符估计也就才6000多个,但是在gbk编码表中只

​ 有英文+中文字符和数字的对应关系。

Shift_JIS

日文编码表,只有日文和英文字符与数字的一一对应关系

Euc-kr

韩文编码表,只有韩文和英文字符与数字的一一对应关系

在那个年代,美国人使用的计算机里面字符编码的标准是ASCII,中国人的是GBK,日本人的是Shift_JIS,

韩国人的是Euc-kr。

在他们的电脑上处理文本文件都是不会出现乱码的,因为在他们的电脑上只有一个标准,无论是存

文件,还是取文件都按照同一张对照表。

文本文件的存取原理

# 以ASCII对照表为例

# 1.存文本文件
	人类通过文本编辑器输入的字符,计算机会按照ASCII对照表翻译成对应的二进制存放在内存
    如果想要永久保存,则直接将内存的二进制写入硬盘即可
    
# 2.取文本文件
    将存放在硬盘中的ASCII格式的二进制读到内存中,然后按照ASCII对照表反解成对应的字符

2.3.3 万国统一 —— unicode

在此之前各国的计算机上还只是只能输入英文字符+本国字符,但是“贪婪”的我们怎么能甘心这样,最好的

方式应该是我的计算机能够输入各种国家的字符,而且保证不乱码。于是unicode(万国码)就应运而生了。

unicode于1990年开始研发,1994年正式公布,具备两大特点:

  1. 固定一个字符对应2Bytes,部分生僻字用4个Bytes

  2. 存在所有语言中的所有字符与数字的一 一对应关系,即兼容万国字符。

  3. 与传统的字符编码的二进制数都有对应关系,详解如下

    很多地方或老的系统、应用软件仍会采用各种各样传统的编码,这是历史遗留问题。此处需要强调:软件是存放于硬盘的,而运行软件是要将软件加载到内存的,面对硬盘中存放的各种传统编码的软件,想让我们的计算机能够将它们全都正常运行而不出现乱码,内存中必须有一种兼容万国的编码,并且该编码需要与其他编码有相对应的映射/转换关系,这就是unicode的第二大特点产生的缘由。
    

比如这个“上”字符,他的unicode对应的数字(4E0A,这里是16进制显示的),不但对应这个上,还对应这

G0-494F,J0-3E45等,也就是unicode第二个特点与传统二进制数也有对应关系。

文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的,存放于硬盘中,则可以转换成任意其他编

码,只要该编码可以支持相应的字符。

# 英文字符可以被ASCII识别
英文字符--->unciode格式的数字--->ASCII格式的数字

# 中文字符、英文字符可以被GBK识别
中文字符、英文字符--->unicode格式的数字--->gbk格式的数字

# 日文字符、英文字符可以被shift-JIS识别
日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字

目前所有多的电脑在内存中字符的编码都是unicode,且不能修改,这一点保证了你可以在电脑上输入任何国家的

字符和你可以打开以前老编码的文本文件,因为他们也和unicode有对应关系,也能反解成对应的字符显示在屏幕

上。

这个时候已经可以在自己的计算机上输入万国的字符了,但是如果你存到硬盘上也采用unicode格式的话,如果绝

大多数字符都是英文字符的话就会造成一种空间的浪费,相对于以前的一个英文字符对应一个Bytes也是一种倒退

虽然说现在存储已经不值钱了,但是当我们写进硬盘时,数据量大的一定比小的带来的IO延迟要高,甚至当我们

由内存写入硬盘时会额外耗费一倍的时间。所以这才是用unicode编码的文件不能直接丢到硬盘的原因。所以将内

存中的unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式,这种格式即utf-8。

2.3.4 UTF-8

utf-8 全称Unicode Transformation Format,即unicode的转换格式

由于unicode的不完美,于是又出现了一种新的字符编码,就是UTF-8,实质上还是unicode,只不过在中英文字

符上做上了区分。这样就完美的解决了unicode带来的存储问题。

  1. 英文字符对应一个Bytes
  2. 中文字符对应3个Bytes

那为何在内存中不直接使用utf-8呢?

utf-8是针对Unicode的可变长度字符编码:一个英文字符占1Bytes,一个中文字符占3Bytes,生僻字用更多的

Bytes存储。

unicode更像是一个过渡版本与之前老的编码格式有这对应关系,我们新开发的软件或文件存入硬盘都采用utf-8

格式,等过去几十年,所有老编码的文件都淘汰掉之后,会出现一个令人开心的场景,即硬盘里放的都是utf-8格

式,此时unicode便可以退出历史舞台,内存里也改用utf-8,天下重新归于统一。

2.4 编码和解码

由字符转换成内存中的unicode,以及由unicode转换成其他编码的过程,都称为编码encode

由内存中的unicode转换成字符,以及由其他编码转换成unicode的过程,都称为解码decode

在诸多文件类型中,只有文本文件的内存是由字符组成的,因而文本文件的存取也涉及到字符编码的问题

3. 字符编码的应用

通过上面上面的知识我们已经对字符编码的理论知识学习完毕了。知道了内存中固定使用unicode无论输入任何字

符都不会发生乱码。我们能够修改的是存/取硬盘的编码方式,如果编码设置不正确将会出现乱码问题。乱码问题

分为两种:

存乱了

就是你输入的字符和你即将存到硬盘所采用的编码方式没有对应关系.
比如: 你输入的是日文,但是你保存文件的时候选择GBK,那么计算机就没有办法通过日文对应的unicode码
     再转换成gbk格式的二进制数字存到硬盘中.因为gbk只和英文字符和中文字符有对应关系.
     这个时候计算机不知道怎么办了,就随便对应就会出现乱码.

读乱了

就是当你用一个文本编辑软件打开一个文本文件时,原本存放的文件的编码如果是gbk格式的二进制,但是你要
用Shift_JIS打开的话,对应的二进制数字是没有对应的字符的,这个时候计算机不知道怎么办了,就随便对应就会出现乱码.

读乱了还好,至少数据是不会丢失的,但是如果你是存乱了,那你原本数据就没办法恢复了。

总结

#1. 保证存的时候不乱:在由内存写入硬盘时,必须将编码格式设置为支持所输入字符的编码格式
#2. 保证读的时候不乱:在由硬盘读入内存时,必须采用与写入硬盘时相同的编码格式

3.1 文本编辑器nodpad++存取文本文件

发现选择日文编码就能正常显示了。

3.2 python程序运行不乱码

首先在你运行python程序的前提是,你的python程序已经开发好了。然后拿着程序交给python解释器进行运行

我们知道python程序就相当于文本文件,python解释器就相当于文本编辑器。那么当它读取文件时,肯定有自己

的默认编码。

python3读取文件的默认编码:utf-8

python2读取文件的默认编码:ASCII

但是如果我们的程序的源文件的编码是gbk,无论是用python2执行,还是python3执行都会出现读取乱码。

这一点开发人员早已想到,可以指定文件头修改默认的编码。在首行添加# coding:gbk(文件保存的编码)

告诉解释器不要用你的默认编码读文件了,用我指定的就行了。

那么首行的字符是以什么编码读取的呢?

没错就是python解释器的默认编码,因为首行全是英文字符,无论哪种编码都能保证读取出来。

保证python程序前两个阶段不乱码的核心法则:

指定文件头,# coding:文件当初存入硬盘时所采用的编码格式

python3的str类型在内存中默认直接存成unicode格式,无论如何都不会乱码。但是python2不是,因为

python1989年诞生,但是unicode1990年才开始推行。所以你懂的。要想保证python2的str类型不乱码

在字符串前+u,例如:x = u"大帅比",强制告诉python2解释器用unicode格式存。

内存的编码使用unicode,不代表内存中全都是unicode,

在程序执行之前,内存中确实都是unicode,比如从文件中读取了一行x="ymn",其中的x,等号,引号,地位都一样,都是普通字符而已,都是以unicode的格式存放于内存中的

但是程序在执行过程中,会申请内存(与程序代码所存在的内存是俩个空间)用来存放python的数据类型的值,而python的字符串类型又涉及到了字符的概念

比如x="ymn",会被python解释器识别为字符串,会申请内存空间来存放字符串类型的值,至于该字符串类型的值被识别成何种编码存放,这就与python解释器的有关了,而python2与python3的字符串类型又有所不同。

4. 补充

4.1 python2的两种字符串类型

python2解释器有两种字符串类型:str,unicode

4.1.1 str

#coding:gbk
x = "上"
print([x]) #['\xc9\xcf']
#\x代表16进制,此处是c9cf总共4位16进制数,一个16进制是4个比特位,
#4个16进制数则是16个比特位,即2个Bytes,这就证明了按照gbk编码中文用2Bytes

# 字符串值会按照文件头指定的编码格式存入变量值的内存空间
# 不指定就是默认的编码格式(ASCII)

gbk存中文需要2个bytes,而存英文则需要1个bytes,它是如何做到的???!!!

gbk会在每个bytes,即8位bit的第一个位作为标志位,标志位为1则表示是中文字符,如果标志位为0则表示为英

文字符。

x=‘你a好’
转成gbk格式二进制位
8bit+8bit+8bit+8bit+8bit=(1+7bit)+(1+7bit)+(0+7bit)+(1+7bit)+(1+7bit)

这样计算机按照从左往右的顺序读:

#连续读到前两个括号内的首位标志位均为1,则构成一个中午字符:你

#读到第三个括号的首位标志为0,则该8bit代表一个英文字符:a

#连续读到后两个括号内的首位标志位均为1,则构成一个中午字符:好

也就是说,每个Bytes留给我们用来存真正值的有效位数只有7位,而在unicode表中存放的只是这有效的7位,至

于首位的标志位与具体的编码有关,即在unicode中表示gbk的方式为:

(7bit)+(7bit)+(7bit)+(7bit)+(7bit)

按照上图翻译的结果,我们可以去unicode关于汉字的对应关系中去查:链接:https://pan.baidu.com/s/1dEV3RYp

可以看到“”上“”对应的gbk(G0代表的是gbk)编码就为494F,即我们得出的结果,而上对应的unicode编码为4E0A,我们可以将gbk-->decode-->unicode

4.1.2 unicode

当python解释器执行到产生字符串的代码时(例如s=u'袁'),会申请新的内存地址,然后将'袁'以unicode的格

式存放到新的内存空间中,所以s只能encode,不能decode

# python2
x = u"上"

# 强制存成unicode

4.2 打印到终端

对于print需要特别说明的是:

当程序执行时,比如x='上' #gbk下,字符串存放为\xc9\xcf

print(x) #这一步是将x指向的那块新的内存空间(非代码所在的内存空间)中的内存,打印到终端,按理说应该是

存的什么就打印什么,但打印\xc9\xcf,对一些不熟知python编码的程序员,立马就懵逼了,所以龟叔自作主张,

print(x)时,使用终端的编码格式,将内存中的\xc9\xcf转成字符显示,此时就需要终端编码必须为gbk,

否则无法正常显示原内容:上

对于unicode格式的数据来说,无论怎么打印,都不会乱码

unicode这么好,不会乱码,那python2为何还那么别扭,搞一个str出来呢?python诞生之时,unicode并未像今天这样普及,很明显,好的东西你能看得见,龟叔早就看见了,龟叔在python3中将str直接存成unicode,我们定义一个str,无需加u前缀,就是一个unicode,屌不屌?

4.3 python3的两种字符串类型

str是unicode

#coding:gbk
x='上' #当程序执行时,无需加u,'上'也会被以unicode形式保存新的内存空间中,

print(type(x)) #<class 'str'>

#x可以直接encode成任意编码格式
print(x.encode('gbk')) #b'\xc9\xcf'
print(type(x.encode('gbk'))) #<class 'bytes'>

很重要的一点是:看到python3中x.encode('gbk') 的结果\xc9\xcf正是python2中的str类型的值,而在

python3是bytes类型,在python2中则是str类型于是我有一个大胆的推测:python2中的str类型就是python3的

bytes类型,于是我查看python2的str()源码,发现

posted @ 2020-08-18 18:39  Mn猿  阅读(205)  评论(0编辑  收藏  举报