一、常见编码
ASCII:ASCII码即美国标准信息交换码(American Standard Code for Information Interchange)。由于计算机内部所有信息最终都是一个二进制值,而每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。标准ASCII 码一共规定了128个字符的编码,这是因为只使用了后面七位,最前面的一位统一规定为0。之后IBM制定了128个扩充字符,这些字符并非标准的ASCII码,而是用来表示框线、音标和其它欧洲非英语系的字母。
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的,而且不同的国家使用不同的字母,有的国家使用的字符也远远超过256个,显然ASCII已经无法解决问题了。那么有没有一种统一且唯一的编码方式呢?答案就是Unicode。
Unicode:Unicode是计算机科学领域里的一项业界标准,Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
但是Unicode也是有问题的,比如“中”字的Unicode编码是十六进制的4E2D,即二进制的100111000101101占15位,也就是说表示这个符号至少需要两个字节,那么怎么知道这两个字节是表示一个字符而不是分别表示两个字符呢?也就是如何区分Unicode和ASCII?
UTF-8:UTF是“Unicode Transformation Format”的缩写,可以翻译成Unicode字符集转换格式。UTF-8 就是在互联网上使用最广的一种Unicode的实现方式,其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示)。UTF-8一个特点,就是它是一种变长的编码方式。它可以使用1~6个字节表示一个符号,根据不同的符号而变化字节长度。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。
字节数? ?|? UTF-8编码(二进制)
1? ? ? ?|? ? 0xxxxxxx?
2? ? ? ?|? ? 110xxxxx 10xxxxxx
? ?3? ? ? ?|? ? 1110xxxx 10xxxxxx 10xxxxxx?
? ?4? ? ? ?|? ? 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx?
? ?5? ? ? ?|? ? 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx?
? ?6? ? ? ?|? ? 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
很明显对于“中”字使用UTF-8编码需要使用三个字节,因此从“中”字的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了“中”字的UTF-8编码,结果为:11100100 10111000 10101101,即十六进制的E4B8AD。
二、Python3编码
Python中的编码问题困扰了我挺久的,尤其是Python2和Python3中还有区别,不过这里我只讨论Python3中的编码问题。
Python3最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分,文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示,而两者之间的转换由编码(encode)和解码(decode)实现。这里可以看一下Python的官方文档:
str.encode(encoding="utf-8",?errors="strict") Return?an?encoded?version?of?the?string?as?a?bytes?object.?Default?encoding?is?'utf-8'.?errors?may?be?given?to? set?a?different?error?handling?scheme.?The?default?for?errors?is?'strict',?meaning?that?encoding?errors?raise? a?UnicodeError.?Other?possible?values?are?'ignore',?'replace',?'xmlcharrefreplace',?'backslashreplace'?and?any? other?name?registered?via??codecs.register_error(),?see?section?Error?Handlers.?For?a?list?of?possible?encodings,? see?section?Stardard?Encodings. ? bytes.decode(encoding="utf-8",?errors="strict") Return?a?string?decoded?from?the?given?bytes.?Default?encoding?is?'utf-8'.?errors?may?be?given?to?set?a?different? error?handling?scheme.?The?default?for?errors?is?'strict',?meaning?that?encoding?errors?raise?a?UnicodeError.? Other?possible?values?are?'ignore',?'replace'?and?any?other?name?registered?via?codecs.register_error(),?see? section?Error?Handlers.?For?a?list?of?possible?encodings,?see?section?Stardard?Encodings.
可以看到str是没有decode方法的,因为本身就是Unicode编码的,而bytes是没有encode方法的,而且无论是encode还是decode默认都是使用UTF-8编码的,当然我们还可以使用其他方式进行编码和解码,比如:
s?=?"博客园" print(s.encode()) print(s.encode("utf-16")) print(s.encode("gbk")) #?b'\xe5\x8d\x9a\xe5\xae\xa2\xe5\x9b\xad' #?b'\xff\xfeZS\xa2[\xedV' #?b'\xb2\xa9\xbf\xcd\xd4\xb0'
那么对于某个数据来说,如果我们不知道它的编码格式,要怎么办呢?在Python3中可以使用chardet模块里的detect方法查看:
import?chardet print(chardet.detect(b'\xe5\x8d\x9a\xe5\xae\xa2\xe5\x9b\xad')) print(chardet.detect(b'\xff\xfeZS\xa2[\xedV')) {'encoding':?'utf-8',?'confidence':?0.87625,?'language':?''} {'encoding':?'UTF-16',?'confidence':?1.0,?'language':?''}
detect方法会返回一个字典,包含编码方式、检测得到的概率和语言信息。可见,用chardet检测编码,使用简单,获取到编码后,再转换为str,就可以方便后续处理。
三、文本编码
首先新建一个文本文档“test.txt”,内容为:“博客园Blogs”,然后选择“另存为”,可以看到默认使用的是ANSI编码:
那么这个ANSI编码是什么呢?不同的国家和地区制定了不同的标准,由此产生了GB2312、GBK、Big5、Shift_JIS等各自的编码标准。这些使用1至4个字节来代表一个字符的各种汉字延伸编码方式,称为ANSI编码。而在简体中文Windows操作系统中,ANSI编码就代表GBK编码。那么假如我们使用了默认的ANSI编码即GBK编码,在读取文本的时候会出现什么情况呢?
with?open("test.txt",?'r')?as?f: ????print(f.read()) #?博客园Blogs with?open("test.txt",?'r',?encoding="gbk")?as?f: ????print(f.read()) #?博客园Blogs with?open("test.txt",?'r',?encoding="utf-8")?as?f: ????print(f.read()) UnicodeDecodeError:?'utf-8'?codec?can't?decode?byte?0xb2?in?position?0:?invalid?start?byte
可以看到使用UTF-8编码打开的时候报错了,原因是有无法解码的字符。那么如果我们保存的时候使用UTF-8编码呢?
with?open("test.txt",?'r')?as?f: ????print(f.read()) #?锘垮崥瀹㈠洯Blogs with?open("test.txt",?'r',?encoding="gbk")?as?f: ????print(f.read()) #?锘垮崥瀹㈠洯Blogs with?open("test.txt",?'r',?encoding="utf-8")?as?f: ????print(f.read()) #?博客园Blogs
很明显看到在使用GBK编码时出现乱码了,原因就是编码和解码的方式不一致,导致最终出现了乱码的情况。
四、URL编码
URL即全球统一资源定位符(Uniform Resource Locator),一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号,这是因为网络标准RFC1738做出了规定。这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,RFC1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。
这里我们可以任意打开一个网页,比如关于编码的百度百科,可以看到浏览器显示的链接没有什么问题:
复制一下,然后粘贴出来看一下:https://baike.baidu.com/item/%E7%BC%96%E7%A0%81/80092,可以看到“编码”两个字被浏览器自动编码成了“%E7%BC%96%E7%A0%81”。这里我们需要知道的是“编”的UTF-8编码为:E7BC96,“码”的UTF-8编码为:E7A081,因此“%E7%BC%96%E7%A0%81”就是在每个字节前面加上一个“%”得到的,也就是说URL路径使用的是UTF-8编码。