Python字符编码

发布时间 2023-12-04 16:17:38作者: Fredette

编码,文字和二进制之间的一个对照表。

【1】阶段一:一家独大(ASCII)

(1)ASCII表的诞生

  • 现代计算机起源于美国,所以最先考虑仅仅是让计算机识别英文字符,于是诞生了ASCII表

(2)ASCII表的特点

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

(3)字母对应ASCII表

  • A-Z:65-90
  • a-z:97-122
  • 0-9:48-57
  • 小写字母对应的数字一定大于大写字母

img

【2】阶段二:诸侯割据(ASCII/GBK/Shift_JIS)

  • 为了让计算机能够识别中文和英文,中国人定制了 GBK

(1)GBK表的特点

  • 只有中文字符、英文字符与数字的一一对应关系
  • 一个英文字符对应1Bytes 一个中文字符对应2Bytes
  • 补充说明
    • 1Bytes=8bit,8bit最多包含256个数字,可以对应256个字符,足够表示所有英文字符
    • 2Bytes=16bit,16bit最多包含65536个数字,可以对应65536个字符,足够表示所有中文字符

(2)各国不同的编码表

  • 为了让计算机能够识别日文字符、英文字符与数字的一一对应关系,日本定制了 Shift_JIS
  • 为了让计算机能够识别韩文字符、英文字符与数字的一一对应关系,韩国定制了 Euc-kr
  • 美国人用的计算机里使用字符编码标准是ASCII
  • 中国人用的计算机里使用字符编码标准是GBK
  • 日本人用的计算机里使用字符编码标准是Shift_JIS

img

(3)多种编码共存的缺点

  • 此时无论是存还是取由于采用的字符编码表一样
    • 所以肯定不会出现乱码问题
  • 但问题是
    • 在美国人用的计算机里只能输入英文字符
    • 而在中国人用的计算机里只能输入中文字符和英文字符....
  • 毫无疑问我们希望计算机允许我们输入万国字符均可识别、不乱码,而现阶段计算机采用的字符编码ASCII、GBK、Shift_JIS都无法识别万国字符
    • 所以我们必须定制一个兼容万国字符的编码表

【3】阶段三:一统天下(unicode)

  • unicode于1990年开始研发,1994年正式公布

(1)unicode的特点

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

(2)字符的存储

  • 文本编辑器输入任何字符都是最新存在于内存中,是unicode编码的
    • 存放于硬盘中,则可以转换成任意其他编码,只要该编码可以支持相应的字符
  • 英文字符可以被ASCII识别
    • 英文字符--->unciode格式的数字--->ASCII格式的数字
  • 中文字符、英文字符可以被GBK识别
    • 中文字符、英文字符--->unicode格式的数字--->gbk格式的数字
  • 日文字符、英文字符可以被shift-JIS识别
    • 日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字

img

【四】编码和解码

【1】编码(encode)

  • 由字符转换成内存中的unicode,以及由unicode转换成其他编码的过程,都称为编码encode
x = '上'  # 在python3在'上'被存成unicode
res = x.encode('utf-8')
print(res, type(res))  # unicode编码成了utf-8格式,而编码的结果为bytes类型,可以当作直接当作二进制去使用
# b'\xe4\xb8\x8a' <class 'bytes'>

# 只有英文字符和数字,要想编码的话,直接使用前缀b --- 字节对象没有encode方法
s = b'Dream123'
print(s, type(s))
# b'Dream123' <class 'bytes'>

img

【2】解码(decode)

  • 由内存中的unicode转换成字符,以及由其他编码转换成unicode的过程,都称为解码decode
  • 在诸多文件类型中,只有文本文件的内存是由字符组成的,因而文本文件的存取也涉及到字符编码的问题
x = b'\xe4\xb8\x8a'
res = x.decode('utf-8')
print(res, type(res))
# 上 <class 'str'>


s = b'Dream123'
res = s.decode('utf-8')
print(res, type(res))
# Dream123 <class 'str'>

img

【五】utf-8的诞生

【1】引言

  • 如果保存到硬盘的是GBK格式二进制,当初用户输入的字符只能是中文或英文
  • 同理如果保存到硬盘的是Shift_JIS格式二进制,当初用户输入的字符只能是日文或英文……
  • 如果我们输入的字符中包含多国字符,那么该如何处理?
  • 多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(GBK格式的二进制)
  • 多国字符—√—>内存(unicode格式的二进制)——X—>硬盘(Shift_JIS格式的二进制)
  • 多国字符—√—>内存(unicode格式的二进制)——√—>硬盘(???格式的二进制)

【2】解决办法(utf-8)

  • 理论上是可以将内存中unicode格式的二进制直接存放于硬盘中的
    • 但由于unicode固定使用两个字节来存储一个字符
  • 如果多国字符中包含大量的英文字符时,使用unicode格式存放会额外占用一倍空间(英文字符其实只需要用一个字节存放即可)
  • 然而空间占用并不是最致命的问题
    • 最致命地是当我们由内存写入硬盘时会额外耗费一倍的时间
  • 所以将内存中的unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式
    • 这种格式即utf-8(全称Unicode Transformation Format,即unicode的转换格式)
    • 我们日常使用的字符编码都是utf8编码,但是,utf系列还有utf16 utf32... utf8mb4
    • utf8只能存储正常的字符,utf8mb4可以存储表情
  • 多国字符—√—>内存(unicode格式的二进制)——√—>硬盘(utf-8格式的二进制)

img

【3】为何在内存中不直接使用utf-8

  • utf-8是针对Unicode的可变长度字符编码:
    • 一个英文字符占1Bytes,
    • 一个中文字符占3Bytes,生僻字用更多的Bytes存储
  • unicode更像是一个过渡版本,
  • 我们新开发的软件或文件存入硬盘都采用utf-8格式
  • 等过去几十年,所有老编码的文件都淘汰掉之后,会出现一个令人开心的场景
  • 即硬盘里放的都是utf-8格式
  • 此时unicode便可以退出历史舞台
  • 内存里也改用utf-8,天下重新归于统一

【六】字符编码的应用

【1】学习字符编码的目的

  • 学习字符编码就是为了存取字符时不发生乱码问题
  • 内存中固定使用unicode无论输入任何字符都不会发生乱码
  • 我们能够修改的是存/取硬盘的编码方式,如果编码设置不正确将会出现乱码问题。

【2】乱码问题

  • 乱码问题分为两种:存乱了,读乱了

(1)存乱了

如果用户输入的内容中包含中文和日文字符,如果单纯以shift_JIS存,日文可以正常写入硬盘,而由于中文字符在shift_jis中没有找到对应关系而导致存乱了

(2)读乱了

  • 如果硬盘中的数据是shift_JIS格式存储的,采GBK格式读入内存就读乱了

(3)总结

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

【七】Python解释器执行文件

【1】Python解释器执行文件的前两个阶段

  • 执行py文件的前两个阶段就是python解释器读文本文件的过程,与文本编辑读文本文件的前两个阶段没人任何区别

  • 要保证读不乱码,则必须将python解释器读文件时采用的编码方式设置为文件当初写入硬盘时的编码格式

  • 如果没有设置,python解释器则才用默认的编码方式,在python3中默认为utf-8,在python2中默认为ASCII

  • 我们可以通过指定文件头来修改默认的编码

  • 在文件首行加上

    # coding: 当初文件写入硬盘时采用的编码格式
    
  • 解释器会先用默认的编码方式读取文件的首行内容

    • 由于首行是纯英文组成,而任何编码方式都可以识别英文字符。

【3】python解释器执行文件的第三个阶段

  • 设置文件头的作用是保证运行python程序的前两个阶段不乱码
    • 经过前两个阶段后py文件的内容都会以unicode格式存放于内存中。
  • 在经历第三个阶段时开始识别python语法
    • 当遇到特定的语法name = '上'(代码本身也都全都是unicode格式存的)时
    • 需要申请内存空间来存储字符串'上'
    • 这就又涉及到应该以什么编码存储‘上’的问题了。
  • 在Python3中
  • 字符串类的值都是使用unicode格式来存储
  • 由于Python2的盛行是早于unicode的
    • 因此在Python2中是按照文件头指定的编码来存储字符串类型的值的(如果文件头中没有指定编码
    • 那么解释器会按照它自己默认的编码方式来存储‘上’)
    • 所以,这就有可能导致乱码问题
# coding:utf-8
# x的值为untf-8格式的二进制
x = '上'

# 打印操作是将x的值,即utf-8格式的二进制交给终端
# 当终端收到后发现并不是unicode(只有unicode才与字符有对应关系)
# 所以终端会执行操作:utf-8二进制---解码-->unicode格式的二进制
# 解码的过程终端会采用自己默认的编码
# 而在pycharm的终端默认编码为utf-8、windows下的cmd终端的默认编码为gbk
# 所以该打印操作在pycharm中显示正常,而在windows下的cmd中则乱码
print(x)
# 在windows下的cmd中运行效果如下
C:\Users\Administrator>python2 E:\aaa.py
涓
  • python2后推出了一种补救措施,就是在字符串类型前加u
    • 则会将字符串类型强制存储unicode
    • 这就与python3保持一致了
    • 对于unicode格式无论丢给任何终端进行打印,都可以直接对应字符不会出现乱码问题
# coding:utf-8
x = u'上' # 即便文件头为utf-8,x的值依然存成unicode