字符编码

1. 字符编码相关的计算机知识

1.1 计算机基础

1.2 文本编辑器存取文件的原理

#1、打开编辑器就打开了启动了一个进程,是在内存中的,所以,用编辑器编写的内容也都是存放与内存中的,断电后数据丢失
#2、要想永久保存,需要点击保存按钮:编辑器把内存的数据刷到了硬盘上。
#3、在我们编写一个py文件(没有执行),跟编写其他文件没有任何区别,都只是在编写一堆字符而已。

1.3 python解释器执行py文件的原理

#第一阶段:python解释器启动,此时就相当于启动了一个文本编辑器
#第二阶段:python解释器相当于文本编辑器,去打开test.py文件,从硬盘上将test.py的文件内容读入到内存中
#第三阶段:python解释器解释执行刚刚加载到内存中test.py的代码( ps:在该阶段,即真正执行代码时,才会识别python的语法,执行文件内代码,当执行到name="dandan"时,会开辟内存空间存放字符串"dandan")

1.4 总结python解释器与文件本编辑的异同

#1、相同点:python解释器是解释执行文件内容的,因而python解释器具备读py文件的功能,这一点与文本编辑器一样
#2、不同点:文本编辑器将文件内容读入内存后,是为了显示或者编辑,根本不去理会python的语法,而python解释器将文件内容读入内存后,可不是为了给你瞅一眼python代码写的啥,而是为了执行python代码、会识别python语法。

2 字符编码介绍

因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。

常见的字符编码:

  • ASCII (英文编码)
  • GB2312 (中国)
  • BIG-5 (中国台湾地区)
  • GBK (中国)
  • Shift_JIS (日本)
  • Euc-kr (韩国)
  • Unicode编码
  • UTF-8编码

编码其实就是让每个文字都有一组数字与之对应

3 字符编码的发展阶段

3.1 阶段一 英文编码

现代计算机起源于美国,最早诞生也是基于英文考虑的ASCII SCII:一个Bytes代表一个字符(英文字符/键盘上的所有其他字符),1Bytes=8bit,8bit可以表示0-2**8-1种变化,即可以表示256个字符 ASCII最初只用了后七位,127个数字,已经完全能够代表键盘上所有的字符了(英文字符/键盘的所有其他字符),后来为了将拉丁文也编码进了ASCII表,将最高位也占用了

3.2 阶段二 各国各自编码

GBK: 2 Bytes代表一个中文字符,1Bytes表示一个英文字符 了满足其他国家,各个国家纷纷定制了自己的编码 日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里

3.3 阶段三 万国码

Unicode和UTF-8

Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。

现在,捋一捋ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。

字母A用ASCII编码是十进制的65,二进制的01000001

字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;

汉字已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101

你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001

新的问题又出现了:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。

所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

4 计算机系统内 编码的工作方式

计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:

所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正是用的UTF-8编码。

5 执行Python程序的三个阶段

阶段一:启动python解释器

阶段二:python解释器此时就是一个文本编辑器,负责打开文件test.py,即从硬盘中读取test.py的内容到内存中

此时,python解释器会读取test.py的第一行内容,#coding:utf-8,来决定以什么编码格式来读入内存,这一行就是来设定python解释器这个软件的编码使用的编码格式这个编码,

可以用sys.getdefaultencoding()查看,如果不在python文件指定头信息#-*-coding:utf-8-*-,那就使用默认的

python2中默认使用ascii,python3中默认使用utf-8

阶段三:读取已经加载到内存的代码(unicode编码格式),然后执行,执行过程中可能会开辟新的内存空间,比如name="dandan"

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

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

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

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

6 python2中的字符串类型(了解)

在python2中有两种字符串类型str和unicode

6.1 Python2中的str类型

当python解释器执行到产生字符串的代码时(例如x='上'),会申请新的内存地址,然后将'上'编码成文件开头指定的编码格式

要想看x在内存中的真实格式,可以将其放入列表中再打印,而不要直接打印,因为直接print()会自动转换编码,这一点我们稍后再说。

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

理解字符编码的关键!!!

内存中的数据通常用16进制表示,2位16进制数据代表一个字节,如\xc9,代表两位16进制,一个字节

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)

6.2 unicode类型

当python解释器执行到产生字符串的代码时(例如s=u'林'),会申请新的内存地址,然后将'林'以unicode的格式存放到新的内存空间中,所以s只能encode,不能decode

#coding:gbk
x=u'上' #等同于 x='上'.decode('gbk')
y=u'下' #等同于 y='下'.decode('gbk')
print([x,y]) #[u'\u4e0a', u'\u4e0b']
print(type(x),type(y)) #(<type 'unicode'>, <type 'unicode'>)

打印到终端

对于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前缀

7 Python3中的字符串

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言,例如:

>>> print('包含中文的str')
包含中文的str

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

如果知道字符的整数编码,还可以用十六进制这么写str

>>> '\u4e2d\u6587'
'中文'

两种写法完全是等价的。

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes

Python对bytes类型的数据用带b前缀的单引号或双引号表示:

x = b'ABC'

要注意区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。

bytes中,无法显示为ASCII字符的字节,用\x##显示。

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

如果bytes中包含无法解码的字节,decode()方法会报错:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8')
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte

如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'

要计算str包含多少个字符,可以用len()函数:

>>> len('ABC')
3
>>> len('中文')
2

len()函数计算的是str的字符数,如果换成byteslen()函数就计算字节数:

>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

可见,1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。

在操作字符串时,我们经常遇到strbytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。

申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:

set-encoding-in-notepad++

如果.py文件本身使用UTF-8编码,并且也申明了# -*- coding: utf-8 -*-,打开命令提示符测试就可以正常显示中文:

py-chinese-test-in-cmd

8 字符串格式化

8.1 占位符

>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串

8.2 format()

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

results matching ""

    No results matching ""