添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

字符编码是任何一门计算机编程语言都会碰到和必须要解决的问题,因为我们最终写的代码是在计算机上运行的,而由于计算机发展的各种 历史原因,导致现在有各种各样的编码呈现于世,所以如果不知道字符编码的基本原理,很有可能你写的代码在不同平台运行就会出现乱码问题。

一、字符编码和乱码问题

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的编码格式一致就不会出现问题