1 字符編碼簡(jiǎn)介
1.1 ASCII
ASCII:American Standard Code for Information Interchange屎蜓。
計(jì)算機(jī)是美國(guó)人發(fā)明的,因此最早只有127個(gè)字符被編碼到計(jì)算機(jī)里半哟,也就是大小寫(xiě)英文字母、數(shù)字和一些符號(hào)个盆,這個(gè)編碼表被稱為ASCII編碼吧凉,比如大寫(xiě)字母A的編碼是65,小寫(xiě)字母a的編碼是97怎栽。
ASCII碼使用指定的7位或8位二進(jìn)制數(shù)組合來(lái)表示128或256種可能的字符丽猬。標(biāo)準(zhǔn)ASCII碼也叫基礎(chǔ)ASCII碼,使用7位二進(jìn)制數(shù)(剩下的1位二進(jìn)制為0)來(lái)表示所有的大寫(xiě)和小寫(xiě)字母熏瞄,數(shù)字0到9脚祟、標(biāo)點(diǎn)符號(hào),以及在美式英語(yǔ)中使用的特殊控制字符强饮。
后128個(gè)稱為擴(kuò)展ASCII碼愚铡。許多基于x86的系統(tǒng)都支持使用擴(kuò)展(或“高”)ASCII。擴(kuò)展ASCII碼允許將每個(gè)字符的第8位用于確定附加的128個(gè)特殊符號(hào)字符胡陪、外來(lái)語(yǔ)字母和圖形符號(hào)沥寥。
但是要處理中文顯然一個(gè)字節(jié)是不夠的,至少需要兩個(gè)字節(jié)柠座,而且還不能和ASCII編碼沖突邑雅,所以中國(guó)制定了GB2312編碼,用來(lái)把中文編進(jìn)去妈经。
全世界有上百種語(yǔ)言淮野,日文編到Shift_JIS里捧书,韓文編到Euc-kr里,各國(guó)有各國(guó)的標(biāo)準(zhǔn)骤星,就不可避免地出現(xiàn)沖突经瓷,結(jié)果就是,在多語(yǔ)言混合的文本中洞难,顯示出來(lái)會(huì)有亂碼舆吮。
1.2 Unicode
有人覺(jué)得太多編碼導(dǎo)致世界變得過(guò)于復(fù)雜谈撒,于是想出來(lái)一個(gè)方法:所有語(yǔ)言的字符都用同一種字符集來(lái)表示酸员,這就是Unicode∨猜裕可以想象柱嫌,如果有一種編碼锋恬,將世界上所有的符號(hào)都納入其中。每一個(gè)符號(hào)都給予一個(gè)獨(dú)一無(wú)二的編碼编丘,那么亂碼問(wèn)題就會(huì)消失与学。
1.3 UTF-8
需要注意的是,Unicode只是一個(gè)符號(hào)集嘉抓,它只規(guī)定了符號(hào)的二進(jìn)制代碼癣防,卻沒(méi)有規(guī)定這個(gè)二進(jìn)制代碼應(yīng)該如何存儲(chǔ)。這里就產(chǎn)生了兩個(gè)嚴(yán)重的問(wèn)題:
- 計(jì)算機(jī)要如何區(qū)分Unicode編碼和ASCII編碼呢掌眠?
- 對(duì)英文字母來(lái)說(shuō)蕾盯,用Unicode編碼時(shí),每個(gè)字符使用三個(gè)蓝丙、四個(gè)字節(jié)表示级遭,那么每個(gè)英文字母前都必然有二到三個(gè)字節(jié)是0,這對(duì)于存儲(chǔ)來(lái)說(shuō)是極大的浪費(fèi)渺尘,文本文件的大小會(huì)因此大出二三倍挫鸽,這是無(wú)法接受的。
UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種Unicode的實(shí)現(xiàn)方式鸥跟。其他實(shí)現(xiàn)方式還包括UTF-16(字符用兩個(gè)字節(jié)或四個(gè)字節(jié)表示)和UTF-32(字符用四個(gè)字節(jié)表示)丢郊,不過(guò)基本不用。
UTF-8最大的一個(gè)特點(diǎn)医咨,就是它是一種變長(zhǎng)的編碼方式枫匾。它可以使用1-4個(gè)字節(jié)表示一個(gè)符號(hào),根據(jù)不同的符號(hào)而變化字節(jié)長(zhǎng)度拟淮。
ASCII編碼實(shí)際上可以被看成是UTF-8編碼的一部分干茉,所以,大量只支持ASCII編碼的歷史遺留軟件可以在UTF-8編碼下繼續(xù)工作很泊。
2 字符與字節(jié)
2.1 字符與字節(jié)
一個(gè)字符不等價(jià)于一個(gè)字節(jié)角虫,字符是人類(lèi)能夠識(shí)別的符號(hào)沾谓,而這些符號(hào)要保存到計(jì)算機(jī)的存儲(chǔ)中,就需要用計(jì)算機(jī)能夠識(shí)別的字節(jié)來(lái)表示戳鹅。
一個(gè)字符往往有多種表示方法(字符編碼)均驶,不同的表示方法會(huì)使用不同的字節(jié)數(shù)。比如字母A-Z都可以用ASCII碼表示(占一個(gè)字節(jié))枫虏,也可以用Unicode表示(占兩個(gè)字節(jié))妇穴,還可以用UTF-8表示(占一個(gè)字節(jié))。
字符編碼的作用就是將人類(lèi)可識(shí)別的字符轉(zhuǎn)換為機(jī)器可識(shí)別的字節(jié)碼模软,解碼就是將機(jī)器可識(shí)別的字節(jié)碼轉(zhuǎn)換成人類(lèi)可識(shí)別的字符伟骨。
Unicode才是真正的字符串饮潦,而用ASCII燃异、UTF-8、GBK等字符編碼表示的是字節(jié)串继蜡。從上面對(duì)各種編碼方式的介紹中回俐,我們也可以了解到,像ASCII稀并、UTF-8仅颇、GBK這些都是編碼方式,是將字符編碼為字節(jié)碼碘举。Unicode只是一個(gè)符號(hào)集忘瓦,它只規(guī)定了符號(hào)的二進(jìn)制代碼,也就是說(shuō)它給每一個(gè)字符一個(gè)獨(dú)一無(wú)二的數(shù)字來(lái)表示引颈。
2.2 編碼與解碼
編碼(encode):在Unicode中耕皮,每一個(gè)字符都有一個(gè)唯一的數(shù)字表示,那么將Unicode字符串轉(zhuǎn)換為特定字符編碼(ASCII蝙场、UTF-8凌停、GBK)對(duì)應(yīng)的字節(jié)串的過(guò)程和規(guī)則就是編碼。
解碼(decode):將特定字符編碼(ASCII售滤、UTF-8罚拟、GBK)的字節(jié)串轉(zhuǎn)換為對(duì)應(yīng)的Unicode字符串的過(guò)程和規(guī)則就是解碼。
簡(jiǎn)單理解:編碼是給計(jì)算機(jī)底層用的完箩,解碼是顯示給人看的赐俗。
3 Python中的默認(rèn)編碼
3.1 Python源代碼文件的執(zhí)行過(guò)程
我們都知道,磁盤(pán)上的文件都是以二進(jìn)制格式存放的弊知,其中文本文件都是以某種特定編碼的字節(jié)形式存放的秃励。對(duì)于程序源代碼文件的字符編碼是由編輯器指定的,比如我們使用Pycharm來(lái)編寫(xiě)Python程序時(shí)會(huì)指定工程編碼和文件編碼為UTF-8吉捶,那么Python代碼被保存到磁盤(pán)時(shí)就會(huì)被轉(zhuǎn)換為UTF-8編碼對(duì)應(yīng)的字節(jié)(encode過(guò)程)后寫(xiě)入磁盤(pán)夺鲜。
當(dāng)執(zhí)行Python代碼文件中的代碼時(shí)皆尔,Python解釋器在讀取Python代碼文件中的字節(jié)串之后,需要將其轉(zhuǎn)換為Unicode字符串(decode過(guò)程)之后才執(zhí)行后續(xù)操作币励。
3.2 默認(rèn)編碼
如果我們沒(méi)有在代碼文件指定字符編碼慷蠕,Python解釋器會(huì)使用哪種字符編碼把從代碼文件中讀取到的字節(jié)轉(zhuǎn)換為Unicode字符串呢?就像我們配置某些軟件時(shí)食呻,有很多默認(rèn)選項(xiàng)一樣流炕,需要在Python解釋器內(nèi)部設(shè)置默認(rèn)的字符編碼來(lái)解決這個(gè)問(wèn)題,這就是“默認(rèn)編碼”仅胞。
Python2和Python3的解釋器使用的默認(rèn)編碼是不一樣的每辟,我們可以通過(guò)sys.getdefaultencoding()來(lái)獲取默認(rèn)編碼:
>>> # Python2
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> # Python3
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
對(duì)于Python2來(lái)講,Python解釋器在讀取到中文字符的字節(jié)碼時(shí)干旧,會(huì)先查看當(dāng)前代碼文件頭部是否指明字符編碼是什么渠欺。如果沒(méi)有指定,則使用默認(rèn)字符編碼"ASCII"進(jìn)行解碼椎眯,導(dǎo)致中文字符解碼失敗挠将,出現(xiàn)如下錯(cuò)誤:
SyntaxError:Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
對(duì)于Python3來(lái)講,執(zhí)行過(guò)程是一樣的编整,只是Python3的解釋器以"UTF-8"作為默認(rèn)編碼舔稀,但是這并不表示可以完全兼容中文問(wèn)題。比如我們?cè)赪indows上進(jìn)行開(kāi)發(fā)時(shí)掌测,Python工程及代碼文件都使用的是默認(rèn)的GBK編碼内贮,也就是說(shuō)Python代碼文件是被轉(zhuǎn)換成GBK格式的字節(jié)碼保存到磁盤(pán)中的。Python3的解釋器執(zhí)行該代碼文件時(shí)汞斧,試圖用UTF-8進(jìn)行解碼操作時(shí)夜郁,同樣會(huì)解碼失敗,出現(xiàn)如下錯(cuò)誤:
SyntaxError:Non-UTF-8 code starting with '\xc4' in file xx.py on line 11, but no encodingdeclared;
see http://python.org/dev/peps/pep-0263/ for details
4 Python2断箫、Python3對(duì)字符串的支持
4.1 Python2
Python2中對(duì)字符串的支持由以下三個(gè)類(lèi)提供:
class basestring(object)
class str(basestring)
class unicode(basestring)
str和unicode都是basestring的子類(lèi)拂酣。嚴(yán)格意義上說(shuō),str其實(shí)是字節(jié)串仲义,它是unicode經(jīng)過(guò)編碼后的字節(jié)組成的序列婶熬。對(duì)UTF-8編碼的str'漢'使用len()函數(shù)時(shí),結(jié)果是3埃撵,因?yàn)閁TF-8編碼的'漢'=='\xE6\xB1\x89'赵颅。
unicode才是真正意義上的字符串,對(duì)字節(jié)串str使用正確的字符編碼進(jìn)行解碼后獲得暂刘,并且len(u'漢')==1饺谬。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
a = '你好'
b = u'你好'
print(type(a),len(a)) # output:(<type'str'>, 6)
print(type(b),len(b)) # output:(<type'unicode'>, 2)
4.2 Python3
Python3中對(duì)字符串的支持進(jìn)行了實(shí)現(xiàn)類(lèi)層次的上簡(jiǎn)化,去掉了unicode類(lèi),添加了一個(gè)bytes類(lèi)募寨。從表面上來(lái)看族展,可認(rèn)為Python3中的str和unicode合二為一了。
class bytes(object)
class str(object)
實(shí)際上拔鹰,Python3中已經(jīng)意識(shí)到之前的錯(cuò)誤仪缸,開(kāi)始明確區(qū)分字符串與字節(jié)。因此Python3中的str已經(jīng)是真正的字符串列肢,而字節(jié)是用單獨(dú)的bytes類(lèi)來(lái)表示恰画。
也就是說(shuō),Python3默認(rèn)定義的就是字符串瓷马,實(shí)現(xiàn)了對(duì)Unicode的內(nèi)置支持拴还,減輕了程序員對(duì)字符串處理的負(fù)擔(dān)。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
a = '你好'
b = u'你好'
c = '你好'.encode('gbk')
print(type(a),len(a)) # output:<class'str'> 2
print(type(b),len(b)) # output:<class'str'> 2
print(type(c),len(c)) # output:<class'bytes'> 4
4.3 比較
對(duì)于單個(gè)字符的編碼欧聘,Python提供了ord()函數(shù)獲取字符的整數(shù)表示片林,chr()函數(shù)把編碼轉(zhuǎn)換為對(duì)應(yīng)的字符:
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(97)
'a'
>>> chr(20013)
'中'
如果知道字符的整數(shù)編碼,還可以用十六進(jìn)制這么寫(xiě)str:
>>> '\u4e2d\u6587'
'中文'
兩種寫(xiě)法完全是等價(jià)的树瞭。
由于Python的字符串類(lèi)型是str拇厢,在內(nèi)存中以Unicode表示爱谁,一個(gè)字符對(duì)應(yīng)若干個(gè)字節(jié)晒喷。如果要在網(wǎng)絡(luò)上傳輸,或者保存到磁盤(pán)上访敌,就需要把str變?yōu)橐宰止?jié)為單位的bytes凉敲。
Python對(duì)bytes類(lèi)型的數(shù)據(jù)用帶b前綴的單引號(hào)或雙引號(hào)表示:x = b'ABC'。
要注意區(qū)分'ABC'和b'ABC'寺旺,前者是str爷抓,后者雖然內(nèi)容顯示得和前者一樣,但bytes的每個(gè)字符都只占用一個(gè)字節(jié)阻塑。
以Unicode表示的str通過(guò)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,內(nèi)容是一樣的陈莽,含有中文的str可以用UTF-8編碼為bytes渤昌。含有中文的str無(wú)法用ASCII編碼,因?yàn)橹形木幋a的范圍超過(guò)了ASCII編碼的范圍走搁,Python會(huì)報(bào)錯(cuò)独柑。
在bytes中,無(wú)法顯示為ASCII字符的字節(jié)私植,用\x##顯示忌栅。
反過(guò)來(lái),如果我們從網(wǎng)絡(luò)或磁盤(pán)上讀取了字節(jié)流曲稼,那么讀到的數(shù)據(jù)就是bytes索绪。要把bytes變?yōu)閟tr湖员,就需要用decode()方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
要計(jì)算str包含多少個(gè)字符,可以用len()函數(shù):
>>> len('ABC')
3
>>> len('中文')
2
len()函數(shù)計(jì)算的是str的字符數(shù)瑞驱,如果換成bytes破衔,len()函數(shù)就計(jì)算字節(jié)數(shù):
>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6
可見(jiàn),1個(gè)中文字符經(jīng)過(guò)UTF-8編碼后通常會(huì)占用3個(gè)字節(jié)钱烟,而1個(gè)英文字符只占用1個(gè)字節(jié)晰筛。
在操作字符串時(shí),我們經(jīng)常遇到str和bytes的互相轉(zhuǎn)換拴袭。為了避免亂碼問(wèn)題读第,應(yīng)當(dāng)始終堅(jiān)持使用UTF-8編碼對(duì)str和bytes進(jìn)行轉(zhuǎn)換。
當(dāng)Python解釋器讀取源代碼時(shí)拥刻,為了讓它按UTF-8編碼讀取怜瞒,我們通常在文件開(kāi)頭寫(xiě)上這兩行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行注釋是為了告訴Linux/OS X系統(tǒng),這是一個(gè)Python可執(zhí)行程序般哼,Windows系統(tǒng)會(huì)忽略這個(gè)注釋吴汪。
第二行注釋是為了告訴Python解釋器,按照UTF-8編碼讀取源代碼蒸眠,否則漾橙,你在源代碼中寫(xiě)的中文輸出可能會(huì)有亂碼。
5 字符編碼的轉(zhuǎn)換
Unicode字符串可以與任意字符編碼的字節(jié)串進(jìn)行相互轉(zhuǎn)換楞卡,如圖:
從上圖可以看出不同字節(jié)編碼之間是可以通過(guò)Unicode來(lái)實(shí)現(xiàn)相互轉(zhuǎn)換的霜运。
Python2中的字符串進(jìn)行字符編碼轉(zhuǎn)換過(guò)程是:
字節(jié)串(Python2的str默認(rèn)是字節(jié)串)-->decode('原來(lái)的字符編碼')-->Unicode字符串-->encode('新的字符編碼')-->字節(jié)串
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
utf_8_a = '我愛(ài)中國(guó)'
gbk_a = utf_8_a.decode('utf-8').encode('gbk')
print(gbk_a.decode('gbk'))
# 輸出結(jié)果:我愛(ài)中國(guó)
Python3中定義的字符串默認(rèn)就是unicode,因此不需要先解碼蒋腮,可以直接編碼成新的字符編碼:
字符串(str就是Unicode字符串)-->encode('新的字符編碼')-->字節(jié)串
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
utf_8_b = '我愛(ài)中國(guó)'
gbk_b = utf_8_b.encode('gbk')
print(gbk_b.decode('gbk'))
# 輸出結(jié)果:我愛(ài)中國(guó)
從上圖中淘捡,也可看出ASCII編碼實(shí)際上可以被看成是UTF-8編碼的一部分。
如果您發(fā)現(xiàn)文中有不清楚或者有問(wèn)題的地方池摧,請(qǐng)?jiān)谙路皆u(píng)論區(qū)留言焦除,我會(huì)根據(jù)您的評(píng)論,更新文中相關(guān)內(nèi)容作彤,謝謝膘魄!