基础部分-2.字符串编码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础部分-2.字符串编码相关的知识,希望对你有一定的参考价值。

一、字符编码历史

1. ASCII

美国人搞了个ASCII码表,把123abcABC%$#(数字、字母、特殊符号) ,全部用10进制的数字表示。例如数字65,代表着“A” ,ASCII码表一共255个数字,基本代表米国常用英文和符号(其实127以后都不是太常用的了)

2. GB2312-->GBK-->GB18030

中国汉字那么多,255个数字显然不够表示。所以中国人发明了GB2312(6千个常用简体汉字),后来又搞了GBK(2万多汉字,并且包含中日韩中的汉字),再后来又搞了一个GB18030(2.7万汉字,包含中国的少数民族语言)

3. Unicode

每个国家不同语言,都需要有自己的编码表,很麻烦。
而且如果日本的软件出口到中国,中国电脑一打开就会乱码(因为没装日本的编码表,软件内文字会乱码)。
再或者,如果一个文件中包含日语、中文、英文,那打开后就乱码了。于是,Unicode国际统一码(2-4个字节)诞生了。 至少用16个2进制表示:11111111 11111111 (2个bytes)

Unicode其实是一张很大的对应关系表,对应着1个字符,在unicode的位置,以及这个字符在ascii码中的位置。
所以,无论你使用何种编码,都能转换成unicode码,而unicode码又能转换成任何其他编码(可以理解unicode左手牵着你编码的字符,右手牵着其他国家的各种编码,额unicode是千手观音,有很多手)。
这样的话,无论你使用GBK、ASCII、或者其他国家对字符进行编码,都能通过这张大表,找到对应其他编码的位置。

4. UTF-8

使用unicode全球人民都很高兴,终于不再出现乱码了,但是美国、英国人不高兴了。
因为以前他们都使用ASCII码,存一个电影1个GB,现在用Unicode变成了2个GB了(因为ASCII码一共255个数字就可以表示美国人使用的常用字符,255转换成二进制:1111 1111(十进制转二进制),是1个bytes。而Unicode至少2个bytes,所有使用unicode编码的文件,就会比ascii码多一倍大小)

于是乎,为了优化unicode,节省字节。又搞出了UTF-8(英文继续用1个字节,欧洲用2个字节,东南亚用3个字节),注意:使用unicode的时候中文是2个字节,现在使用UTF8变成了3个字节表示了,这占空间啊,很蛋疼,但没办法。

5. 其他知识:

  • 数据文件存到硬盘上是2进制的,像这样: 01010100100101001(尽然没逗号,那计算机怎么断句呢?其实计算机会一次性读取8位2进制,不足8位的补0)

  • 你使用ascii码对你的文件编码,并保存到硬盘。当你打开的时候(就是读到内存的时候),就必须也要指定使用ascii码打开。如果你错误的使用GBK打开,那肯定乱码了。总的来说,什么方式存进去的,什么方式读出来。

  • windows系统默认使用GBK编码,也就是说,当你存一个文件到磁盘的时候,如果你不指定使用什么编码,默认使用GBK给你编码(Mac、 Linux系统默认UTF8编码)

二、Python2与Python3编码不同

  • Python3 读取文件的流程:

    1. Python3解析器,按照文件头定义的编码进行解析。(如果你在文件头,不指定编码方式。python3默认使用UTF-8去读取)
    2. Python3自动把你的编码格式,转换成Unicode到内存中。
    3. 按照代码内容语义分析,去解析执行。
    4. 所有的变量、字符都是以Unicode编码去声明

总结:在Python3中,无论读取中文、英文或其他国家语言编码的文件,在打印的时候都不会乱码,因为都进行了自动转换,变成了Unicode。(现在的所有操作系统,都支持Unicode编码)

  • Python2 读取文件的流程:

  1. Python2默认使用ASCII编码,去尝试读取文件。
  2. Python2把ASCII编码加载到内存中(注意:不会像Python3一样,帮你自动转换成Unicode到内存哦)
  3. 如果你文件中包含中文,直接就乱码了,因为ASCII表中没有中文啊。
  4. 别急,通常在编写Python2文件时候,都在文件头加:#coding: utf-8,目的是想告诉Python2解释器,在读取文件时,别在使用默认的ASCII了,要使用UTF-8编码去读取。
  5. 这下,终于把代码以UTF-8读取到内存了。
  6. 还没完,如果你使用Windows电脑,Windows默认打印终端使用GBK编码,在终端打印的时候,你发现依然还是乱码了(崩溃了~)。
  7. Windows是支持Unicode或GBK打印的,所以,还需要你把内存中的UTF-8手动转换成Unicode或者GBK,才能正常打印。
  8. 如何手动转换呢:
    • UTF8 ---> Unicode 把UTF8转换成Unicode的过程,称为:解码(decode)
    • Unicode ---> UTF8/GBK 把unicode编码转换成UTF8或者GBK的过程,称为:编码(encode)
  • 总结一下:

  1. Python3文件默认编码UTF-8(默认用utf-8读到内存),在内存中,python3的字符串编码是unicode(Python3自动帮你把utf-8转成了unicode到内存中了)
  2. Python2文件默认编码ASCII码(默认用ASCII码读到内存),在内存中,python2字符串默认你按照什么编码读出来的,在内存就是什么编码(如果你声明了文件头,就按照头声明来编码读到内存中)。
  3. Python2中,Unicode是个单独的数据类型。

三、深入Python2字符编码

  • 1. 问题出现了

    想判断数据类型,使用type()函数。python2中unicode是一种单独的数据类型,而GBK和UTF-8使用type()函数判断类型时,都返回str类型。这样的话,我怎么知道,到底是GBK还是UTF8呢?有人说可以使用len()函数去判断,因为GBK是使用2个字符代表一个中文字,而UTF8使用3个字符代表一个中文字。如下:

#!/usr/bin/env python2.7
#encoding:utf-8
s = "中"

#unicode
s2 = s.decode("utf-8")
print "我运行在python2中,我声明了文件头是UTF8。虽然python2不会自动把我转换成unicode,但我可以自己使用decode函数手工转换,你看,我现在就变成了unicode编码了,我在python2中表现的数据类型是:%s " %type(s2)

#GBK
s3 = s2.encode("gbk") #注意:s2经过上一步的转换,已经变成了unicode编码了。s3现在相当于,在用encode函数把unicode编码成了GBK。
print "我是gbk编码的,我在python2中的数据类型是:%s, 我的长度是:%s" % (type(s3),len(s3))

#UTF8
s4 = s2.encode("utf-8")
print "我是utf-8编码的,我在python2中的数据类型是:%s , 我的长度是:%s " %(type(s4),len(s4))
#运行结果:
我运行在python2中,我声明了文件头是UTF8。虽然python2不会自动把我转换成unicode,但我可以自己使用decode函数手工转换,你看,我现在就变成了unicode编码了,我在python2中表现的数据类型是:<type ‘unicode‘>

我是gbk编码的,我在python2中的数据类型是:<type ‘str‘>, 我的长度是:2

我是utf-8编码的,我在python2中的数据类型是:<type ‘str‘> , 我的长度是:3

问题确实存在,gbk编码、utf-8编码,在python2中的数据类型都是str。
我如何知道一个字符串到底是gbk编码的还是utf8编码的呢?
虽然可以看长度,gbk用2个字节,代表1个中文,utf-8用3个字节,代表1个中文汉字。这没错,那是因为举得例子简单,如果无数个字节,比如这样:‘\xd6\xd0...bulabulabula一大堆‘,让你判断,这些字节的编码是什么?那怎么办呢?

  • 2. 难道是巧合?

1. 突然想起来,有那个Unicode对应关系表啊..(http://www.unicode.org/charts/ 下载:CJK Unified Ideographs (Han) )
>>> s = "中"
>>> s_unicode = s.decode("utf-8")
>>> s_unicode
u‘\u4e2d‘    
>>>
>>> s_gbk = s_unicode.encode("gbk")
>>> s_gbk
‘\xd6\xd0‘  
>>>
>>> s_utf8 = s_unicode.encode("utf-8")
>>> s_utf8
‘\xe4\xb8\xad‘ 

总结下,这个"中"字:
unicode编码是长这样: u‘\u4e2d‘    
gbk编码是长这样的: ‘\xd6\xd0‘
UTF8编码是长这样的: ‘\xe4\xb8\xad‘
2. 赶紧下载,并打开Unicode对应表:
  • 搜索"中"字,恩,"中"字旁边的Unicode确实写着:4E2D。
  • 但我们关心的是GBK啊,GBK上写着: G0-5650(16进制)。
  • 这5650 和 我们上面打印的‘\xd6\xd0‘也不完全对啊,但是貌似对了2位。6和0对了,5没对。
3. gbk编码打印是这样的:‘\xd6\xd0‘,这是16进制,换成2进制试试看。
  • d6(16进制)--->11010110(2进制) PS: 4位2进制,代表1个16进制,所以 1101 代表d(13) 0110代表6
  • d0(16进制)--->11010000(2进制)
4. 即便把16进制换成2进制,感觉也和G0-5650没啥关系啊,这样,把上面的2进制的第一位,去掉不算****,试试看:
  • \xd6\xd0 中的d6,11010110(2进制)--->1101 0110--->去掉每组第1位不算--->0101 0110--->转成16进制--->56

  • \xd6\xd0 中的d0, 11010000(2进制)--->1101 0000--->去掉每组第1位不算--->0101 0000)--->转成16进制--->50
5. 我擦嘞~,当去掉1位后,就正好是5650。和Unicode表上的GBK编码对应上了...为什么要去掉1位呢?
  • 3. 原来是这样

GBK是兼容ASCII码的,如何实现的?

首先:GBK是每2个字节代表1个中文,ASCII码是1个字节代表1个英文字母的。

如果给你一个2个2进制(2个字节)11010110 11010000,你怎知道是代表1个中文,还是2个ASCII码的英文字符呢?

正好,ASCII码常用的就127个,7位2进制最大就是127,就可以表示了,还剩下1位。

中国人设计GBK的时候,考虑到想兼容ASCII码。想到,既然ASCII码的127往后至255都不常用。那我们利用剩下的这1位做文章。

  • [x] 如果,2个2进制(2个字节),每个2进制的第1位,都被设置成了1,它就是中文。
  • [x] 如果,1个2进制(1个字节),第一位是1,那就是按照ASCII编码,是英文。

举例:11010110 11010000 这2个二进制,第1位都被都设置了1,这是个中文。

总结:

  • GBK编码中,为了兼容ASCII码,1个2进制的字节,第一位,如果设置成1,叫高字节。如果设置成0,代表低字节。2个连续的高字节,就是中文。**
  • Unicode表中,"中"字符--> 本来应该用d6d0表示,但是它忽略了高字节--->所以变成了这样 G0-5650
  • 其实,UTF-8 也是利用了高字节,来区分ASCII编码的。不然一堆连续的2进制,怎么判断啊?

四、Python str和bytes类型

1. python2中的str和bytes没啥区别

python2
>>> s = "中"
>>> print s
中               #称之为字符串 (其实是字形)
>>> s
‘\xe4\xb8\xad‘   #字符串"中"字,在编码表里的位置。 是二进制数据串(只不过16进制表现形式)

其实按理说,print 应该打印 ‘\xe4\xb8\xad‘ 这个位置,但是print帮你打印了这个位置对应的"中"字 (其实这个"中"字就是一个图片字形)

总结:

  1. 根据2进制的数据串,在编码表中找到对应的关系,然后在找到的那个字形,就是字符串。
  2. 一堆二进制数据串儿,python称他为bytes(注意有 s结尾,复数)。
  3. 概念上"中"字符串(字形)和 ‘\xe4\xb8\xad‘二进制串儿bytes 不是一种东西,但是本质上,却都指向了相同的一个东西。
  4. 所以,python2中,直接把字符串"str" 类型等于了 bytes类型。

实例:

#python2里,把bytes类型和字符串类型,都称为字符串"str",不区分,如下:。

>>> s_str = "中"
>>> print type(s_str)
<type ‘str‘>   #字符串就是str类型
>>>
>>> s_bytes = b"中"   #b"字符串" -->定义一个bytes类型
>>> print type(s_bytes)
<type ‘str‘>   #bytes类型 也是 str类型
>>>

2. 既然python2的bytes类型和字符串str是一会儿事儿,为什么还要搞一个bytes类型呢( b"字符串" ) ?

  • 因为之所以能显示中文字形,是因为二进制串儿bytes,在Unicode编码表中找到了对应的图形,然后给展示出来。
  • 但图片、视频读到内存中,图片没编码(没unicode这种东西),那怎么办呢?实际上,图片是一堆2进制流,对应成了屏幕像素快颜色,最终展示出来的。
  • 既然图片是一堆二进制流,那么这一堆数据流,怎么表示他们呢?就只能用bytes类型表示。
  • print图片会乱码,因为图片又不是文字,如果你非要print图片,那print就会尝试把图片里的二进制流到unicod编码表中找对应关系,但最终肯定会乱码的。
  • 也就是说,字符、图片、视频等,都是bytes类型。但字符因为有Unicode那张表存在,可以找到对应关系。
    所以字符中的bytes,可以转成字符串str(字形),而字符串"str",也肯定能转换成bytes。
  • 但图片、音频中的bytes,则不能转换成字符串str(因为他们本来也不是字,是图啊..)

3. Python3 数据保存和传输

  • Python3的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes,像这样:x = b‘ABC‘

  • 以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
#Python3
>>> type("ABC")
<class ‘str‘> 

>>> ‘ABC‘.encode(‘ascii‘)  #把英文的str变成了bytes
b‘ABC‘

>>> ‘中文‘.encode(‘utf-8‘) #把中文的str变成了bytes
b‘\xe4\xb8\xad\xe6\x96\x87‘
  • 反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:
>>> b‘ABC‘.decode(‘ascii‘)  #把英文bytes转换成了str
u‘ABC‘

>>> type(b‘ABC‘.decode(‘ascii‘)) #通过type()函数查看,python3中str就是unicode类型。
<type ‘unicode‘>

>>> b‘\xe4\xb8\xad\xe6\x96\x87‘.decode(‘utf-8‘) #把中文的bytes转成str
u‘\u4e2d\u6587‘
>>> print(b‘\xe4\xb8\xad\xe6\x96\x87‘.decode(‘utf-8‘)) #打印时,就可以显示字形了。
中文

#说明:如果bytes中包含无法解码的字节,decode()方法会报错
  • len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:
>>> len(‘ABC‘)
3
>>> len(‘中文‘)
2
>>>
>>> len(b‘ABC‘)
3
>>> len(b‘\xe4\xb8\xad\xe6\x96\x87‘)
6

4. 总结

  • Python2
  1. str = bytes
  2. 为什么要有bytes类型?因为要表示图片和音频等,二进制格式的数据。
  3. python2,即便以utf-8编码的字符串,在windows上仍然不能显示(windows默认是GBK)。
    怎样才能正常显示?手动转unicode编码decode("utf8")。
    还有一个办法,在pythno2中,定义一个字符串时,在前面加个u,例如:u"中",这时候打印类型print type,结果: <type ‘unicode‘>
  4. python2 中,unicode类型可以表示字符串,还可以使用str表示字符串。
    有点儿乱,但是unicode是为了显示全球文字,而设计的类型。如果只有英文字符,str类型就够了。
  5. 以utf-8、gbk等编码的文件,加载到内存时,还依然是 utf-8、gbk (不帮你转换unicode,读到什么,就是什么)
    所以说,python2中,如果以gbk格式编码的文件,加载到内存还是gbk,在print打印时,就是gbk格式的bytes。
  • Python3
  1. 以utf-8 gbk等编码的文件,加载到内存时,自动帮你转换成unicode。所以,在python3中看到的str字符串,都是unicode格式编码的bytes。
  2. 也就是说python3,默认支持了全球化语言(因为str都是unicode编码的),
  3. python3中已经没有了单独的unicode类型了,因为str就是Unicode,既str = unicode。
  4. 在python2中,str = bytes,之所以多个bytes类型是为了表示图片等。而python3 str是str (str = unicode), bytes就是bytes,没关系。
  5. 注意,在内存中可以是unicode展示,这没问题。但当你存储到硬盘、网络传输,当然不能用unicode存(unicode只是张对照表),需要unicode转换成gbk或utf-8编码格式的,然后换成bytes去存储或传输。
    也就是说,python3中 str(unicode编码)就仅仅是打印使用的,真正字符串传输、或者存盘,都会转换成bytes。
  • 那为啥Python2有 str、Unicode,而Python3就只有str类型了(python3中str就是Unicode)?

因为在python2初期开发的时候,就没考虑到全球化问题,默认用的ASCII码。后来python普及后,各国开发者呼吁支持全球化语言,所以python2没办法,单独又造了一个Unicode类型。

#python2实例
>>> s = "str"
>>> s_unicode = s.decode("utf-8")   #python2中,unicode类型是单独的。
>>> s_gbk = s_unicode.encode("gbk") #把unicode编码成gbk
>>> type(s_gbk)
<type ‘str‘>            #打印时发现,gbk编码的类型显示为str。

#python3实例
>>> s = "str"         #python3 字符串str都是unicode
>>> s_gbk = s.encode("gbk") #把unicode编码成gbk
>>> type(s_gbk)
<class ‘bytes‘>       #打印时发现, gbk编码的类型是 bytes。(不再和python2一样是str类型了)

结论:
1. 在python2中,str = bytes,之所以多个bytes类型是为了表示图片等二进制流。
2. 在python3中,字符串str就是str字形 (str都是由unicode编码), bytes就是bytes,明确告诉你,你想看字符串str的字形,必须的是unicode编码。而图片二进制流,没办法编码和解码,传输时一律使用bytes类型。

参考链接:

以上是关于基础部分-2.字符串编码的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 代码片段

递进迭代敏捷

计算机基础 数据类型 流程控制 字符编码 文件操作

Python基础-字符编码

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解!(转载)

如何测试文本片段是不是是 Quoted-printable 编码的