字符编码是任何一门计算机编程语言都会碰到和必须要解决的问题,因为我们最终写的代码是在计算机上运行的,而由于计算机发展的各种
历史原因,导致现在有各种各样的编码呈现于世,所以如果不知道字符编码的基本原理,很有可能你写的代码在不同平台运行就会出现乱码问题。
一、字符编码和乱码问题
1、什么是字符编码
我们知道,计算机只处理二进制的数据,所以,我们最终的代码都会编译成计算机能识别的二进制数据。比如字母A,对应二进制数1011,字母B,对应二进数1100等等,这种我们能看到的、使用到的
字符和计算机能处理的二进制数字的对应关系
,就可以绘制一张对应表,这就是字符编码表。
2、乱码这种问题是怎么产生的呢?
根本原因就是:
对同一个字符串在读和写的时候,使用了不同的字符编码表
比如,我们用GBK字符编码来解释字符串‘我爱你’,编译成二进制数是“1010”,然后我们在读取的时候,用了其他字符编码‘utf-8’,那么在‘utf-8’字符编码表看来,你这个二进制“1010”代表的就不是字符串“我爱你”,可能其他乱七八糟的东西,这样就产生了乱码。
二、常用字符编码
1、ASCII编码
最早的字符编码,包含字母、数字和一些常见的符号,只有
一个字节
,所以最多能表示 2
8
=256 个字符数。由于计算机是美国人发明的,所以ASCII编码表只有
127个字符
,因为在他们看来,已经足够用了,比如大写字母A的编码是65,小写字母z的编码是122。所以如果你的代码中只出现在127个字符中的字母、数字或者符合,用ASCII编码已经可以使你的代码在所有平台上运行都不会出现乱码问题,因为其他的编码基本都会兼容ASCII。
2、GB2312/GBK
中国博大精深的汉字当然无法用ASCII编码来表示了,那么我们国人就自己定义了适合中国汉字的编码表——GB2312/GBK。这就是汉字的国标码,专门用来表示汉字,是
双字节
编码,。其中
gbk编码能够用来同时表示繁体字和简体字
,而gb2312只能表示简体字,gbk是兼容gb2312编码的。
3、unicode
你可以想得到的是,全世界有上百种语言,类似的,日文和韩文等其他语言也有这个问题。为了统一所有文字的编码,Unicode 应运而生。Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题了,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,规定最少
2个字节
(16位),即:2
16
= 65536,注意:此处说的的是最少2个字节,可能更多。
如果统一成Unicode编码,乱码问题从此消失了。
4、UTF-8
使用全部使用Unicode编码,虽然解决了乱码问题,但是随即又产生了一个新问题,资源浪费!怎么说呢,因为Unicode编码是最少2个字节的,也就是说之前用ASCII编码表示的字母A,本来一个字节就可以表示的东西,现在需要多一倍的的存储空间。
所以,本着节约的精神,又出现了把Unicode编码转化为“
可变长编码
”的UTF-8编码,UTF-8是Unicode的扩展之一,还有什么UTF-9,UTF-16什么的,比较少用,最常用的还是UTF-8。
UTF-8编码可以把一个Unicode字符根据实际大小编码成1-6个字节,常用的
英文字母被编码成1个字节,汉字通常是3个字节
,只有很生僻的字符才会被编码成4-6个字节。
ASCII
Unicode
UTF-8
01000001
00000000 01000001
01000001
01001110 00101101
11100100 10111000 10101101
5、编码总结
UTF是为unicode编码设计的一种在存储和传输时节省空间的编码方案。在
计算机内存中,统一使用Unicode编码
,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码
。
比如:用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:注意下图不同状态对应不同的编码格式。
三、Python的字符编码
1、python 2中默认编码查看和转换(仅限python 2中)
#查看默认字符编码
>>> import sys
>>> sys.getdefaultencoding()
'ascii’
#设置默认字符编码
>>> reload(sys)
>>> sys.setdefaultencoding("utf-8")
>>> sys.getdefaultencoding()
'utf-8'
2、encode和decode
encode(编码)
:Unicode ==> utf-8或者gbk(字节流)
decode(解码)
:utf-8或者gbk(字节流) ==> Unicode
一个是编,一个是解,怎么样才能比较好记忆和理解呢?我是这么认为的,
‘utf-8’或者‘gbk’是具体的编码格式,所以这两个要‘解(decode)
’,解完之后就成了
Unicode,它好比一种中间编码的状态(仅仅为了好理解)
,虚无定型,这个时候就可以指定一种具体的格式进行
‘编(encode)’
。
流程是这样的:UTF-8(解码)–> Unicode –>(编码) GBK
3、python 2和3中‘str’类型的本质区别
在理解为什么python 3.x就解决了乱码这个问题之前,我们首先要知道一个事实,python 2和python 3的str是有本质区别的。
在
python 2中的str是“某种具体的编码格式”
,比如‘utf-8’,‘gbk’,‘ascii’,它本身存储的就是字节码(bytes),虽然在读取的时候比较方便,但是比较局限,如果要从gbk到utf-8,就得先转换成Unicode。
>>> s = "我爱你"
'\xe6\x88\x91\xe7\x88\xb1\xe4\xbd\xa0'
python 3的str格式定义变更为”Unicode类型的字符串“
,在默认情况下,被引号框起来的字符串,本质是使用Unicode编码的。也就是说
python3中的str就相当于python2中的unicode
。一种可以理解为万金油的格式,作为一种中间编码,不管是要到gbk或者utf-8,都非常方便。
首先有了个初步的认识,我再进一步探究
4、python 2和3中‘str’类型的表现形式区别
字符串解码(转成Unicode)
python 2的字符串有两种解码方式:
① 使用decode(编码格式):可以指定字符编码
② 在字符串前面加‘u’:不能指定字符编码,使用默认编码方式来解码
Python 2
#① 第一种解码方式
>>> s = "我爱你"
>>> s.decode('utf-8')
u'\u6211\u7231\u4f60'
#② 第二种解码方式
>>> u"我爱你"
u'\u6211\u7231\u4f60'
Python 3的字符串不能直接解码
,因为前面说过了,Python 3的str本质就是Unicode,我已经是Unicode格式了,还需要解码么?所以在Python 3中,字符串是没有decode方法的。
Python 3
>>> s = "我爱你"
>>> s.decode('utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'decode'
5、encode和decode的使用场景
前面有说到,为了节约空间资源,在网络传输或者写入磁盘的时候,最终会编码为‘utf-8’或者‘gbk’格式的字节码,
在Python 2中,因为Python 2的内存数据直接就是已经编码的字节码,所以不需要进行encode或者decode,可以直接读取。
在Python 3中,从网络或磁盘接收到的数据是已经编码的字节码(utf-8’或‘gbk’),而Python需要根据格式进行解码(decode)成Unicode格式,相反如果想从内存、磁盘或者网络中写入数据,python 3要先进行编码(encode),一句话总结:
接收解码,发送编码
。
为了便于记忆,下面简易的画了个图帮助理解。
6、字符编码使用
python 2
在python 2中,默认使用ASCII编码,所以如果你的代码出现中文,妥妥的报SyntaxError,因为ASCII不认识中文。
所以在python 2中为了解决中文问题,都会在源文件的头部加上以下信息:
#!/usr/bin/env python3
# -*- coding: utf-8 -*- 或者 #coding=utf-8
第一行注释:为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
第二行注释:为了告诉Python解释器,按照UTF-8编码去处理字符串。
python 3
在python 3中,默认使用‘utf-8’编码,所以在编写python 3代码时,如果要支持中文字符串的处理,终于不需要手动指定编码格式。
7、实例演示
到此为止,你以为就结束了么?非也非也,你以为在Python 2中配置了‘utf-8’,或者直接使用python 3就一定不会出现乱码问题了么?答案肯定不是,如果不注意,仍然会出现乱码。
① 我们下面在windows的CMD来做一个小示例:
在python 3.7中执行脚本,脚本只有一条语句:print('科比'),按理来说,python 3.7肯定是支持中文的啦,为什么还是会报错?
因为我们这里设了一个小小的坑,我的test.py源代码脚本的保存格式是‘GBK’的,所以当python解释器拿到这个字符串(GBK格式),然后用‘utf-8’去decode成Unicode这个过程当然会有问题,所以直接报错了。
所以最终,我们的
源代码脚本的编码格式和设置的编码格式要保证一致
。
② 到这里还没完,对,就是没完没了了。下面再看一个示例:
咦,不对啊,我‘utf-8’也申明了,源代码文件格式也是‘utf-8’了,为什么还给我打印乱码?
首先,我们要知道运行这个脚本,打印中文“科比”这条语句是涉及到了两个角色的,一个自然就是我们的
python解释器
,一个是我们的
控制台
,在windows是cmd,linux是shell,python要打印字符串,会调用控制台进行显示的,所以会把要显示的字符串数据传给控制台。
知道有这么个隐藏的动作,就好理解为什么会打印出乱码了?如果我前面的讲解你都能懂的话,你应该能猜到了,首先,我是在python 2中打印这条语句,我虽然设置了默认编码格式为‘utf-8’,然后编码成字节码(utf-8格式)传送给我们的控制台cmd,而cmd拿到这串数据,用‘gbk’去解码,注意,这里就是产生乱码的原因!
windows的cmd是‘gbk’编码格式的,而我们的字符串是‘utf-8’格式的,所以产生了乱码
。
如果你把这个脚本文件拿到linux上去运行,正常打印,因为我们linux的shell是‘utf-8’的!当然在windows用python 3去运行这个脚本,也没有问题,为什么?因为python 3的str是Unicode类型的,cmd拿到就可以直接编码成‘gbk’格式的。
四、python编码使用总结
1、尽量使用python 3,Unicode类型的字符串可以不用担心跨平台时乱码问题
2、保证申明的脚本编码格式和源文件保存的编码格式一致
3、如果一定要在python 2中使用中文字符串,可以用decode或者u‘xxxx’方法解码成Unicode类型
总之,记住一句话,从头到文,从开始到结束,从输入到输出,保证
decode和encode的编码格式一致就不会出现问题
。