字符與字節(jié):計(jì)算機(jī)存儲(chǔ)的一切數(shù)據(jù),都是由一系列的01比特序列組成的,包括文本字符、圖像、音視頻、軟件等等。8位01比特序列構(gòu)成一個(gè)字節(jié)。而字符就是一個(gè)符號(hào),如一個(gè)漢字、一個(gè)字母、一個(gè)標(biāo)點(diǎn)。
通俗來說,字節(jié)是計(jì)算機(jī)的語言,字符是人的語言。如果想要讓人和機(jī)器通信交流,就需要進(jìn)行編碼和解碼。而編解碼的一般套路大概就是:給待編碼的目標(biāo)集合(字符,或者其他任何符號(hào)、資源等)給定一個(gè)唯一ID,把這個(gè)ID對應(yīng)的二進(jìn)制保存下來,如此種種。
一、ASCII、Unicode、UTF-8、GBK是個(gè)什么鬼
這里引用一下知乎上盛世唐朝的回答,個(gè)人覺得還是比較通俗易懂的。
- 中國人民通過對ASCII編碼的擴(kuò)充改造,產(chǎn)生了GB2312編碼,可以表示6000多常用漢字。
- 漢字實(shí)在是太多了,包括繁體和各種字符,于是產(chǎn)生了GBK編碼,它包括了GB2312中的編碼,同時(shí)又?jǐn)U充了很多。
- 中國是個(gè)多民族國家,各個(gè)民族幾乎都有自己獨(dú)立的語言系統(tǒng),為了表示那些字符,繼續(xù)把GBK編碼擴(kuò)充為GB18030編碼。
- 每個(gè)國家都像中國一樣,把自己的語言編碼,于是出現(xiàn)了各種各樣的編碼,如果你不安裝相應(yīng)的編碼,就無法解釋相應(yīng)編碼想表達(dá)的內(nèi)容。
- 終于,有個(gè)叫ISO的組織看不下去了。他們創(chuàng)造了一種編碼UNICODE,這種編碼非常大,大到可以容納世界上任何一個(gè)文字和標(biāo)志。所以只要電腦上有UNICODE這種編碼系統(tǒng),無論全球哪種文字,只需要保存文件的時(shí)候,保存成UNICODE編碼就可以被其他電腦正常解釋。
- UNICODE在網(wǎng)絡(luò)傳輸中,出現(xiàn)了兩個(gè)標(biāo)準(zhǔn)UTF-8和UTF-16,,分別每次傳輸8個(gè)比特位和16個(gè)比特位。
- 有了UTF-8,為什么國內(nèi)還有這么多使用GBK等編碼的人?因?yàn)閁TF-8等編碼體積比較大,占用空間比較多,如果面向的使用人群絕大部分都是中國人,用GBK等編碼也可以。
一言以蔽之,Unicode和GBK、ASCII同是字符集,用來表示字符(符號(hào)),前者的表達(dá)能力包括后兩者;ASCII是最基本的單字節(jié)字符集,可直接存儲(chǔ)和傳輸;而UTF-8只是Unicode字符集的一個(gè)標(biāo)準(zhǔn),一種實(shí)現(xiàn)方式而已。
如果用一幅圖來表示的話,大概長成下面這樣子:

二、encode和decode該用哪一個(gè)
編碼(encode)就是將字符轉(zhuǎn)化成字節(jié)序列的過程。反之,解碼(decode)就是將字節(jié)序列轉(zhuǎn)換成字符的過程。兩者是一個(gè)互逆的過程,編碼是為了存儲(chǔ)傳輸,解碼是為了顯示閱讀。
1、Python的編碼為什么那么蛋疼?
原因有二。
其一是因?yàn)镻ython2使用ASCII字符編碼作為默認(rèn)編碼方式,而ASCII不能處理中文。那為什么不用UTF-8呢?因?yàn)镚uido老爹當(dāng)年正式發(fā)布Python第一個(gè)版本的時(shí)候是1991年2月,而Unicode是1991年10月發(fā)布的。Python出生時(shí),UTF-8還沒出生。
其二是因?yàn)镻ython2把字符串的類型搞成兩種,str和unicode,以致于大家經(jīng)常被搞糊涂了。
2、str與unicode
Python2中字符串有str和unicode兩種類型。str本質(zhì)上是一串字節(jié)序列,由如下示例代碼可以看出,str類型的”心“打印出來的是十六進(jìn)制'\xe5\xbf\x83'。
>>> sys.stdin.encoding
'UTF-8'
>>> sys.stdout.encoding
'UTF-8'
>>> s = '心'
>>> type(s)
<type 'str'>
>>> s
'\xe5\xbf\x83'
>>>
而unicode類型的u”心“對應(yīng)的unicode符號(hào)是u'\u5fc3'。
>>> u = u'心'
>>> type(u)
<type 'unicode'>
>>> u
u'\u5fc3'
>>>
實(shí)驗(yàn)環(huán)境為UTF-8。所以上述三個(gè)字節(jié)'\xe5\xbf\x83'即是“心”的utf-8編碼的字節(jié)碼。這里簡單說明一下這種轉(zhuǎn)換關(guān)系。
“心”的Unicode編碼為
u'\u5fc3'即U+5fc3,在 U+0800 和 U+FFFF 之間,我們把5fc3的二進(jìn)制編碼0101 1111 1100 0011依次替換到1110xxxx 10xxxxxx 10xxxxxx里的 x 位置上,不夠的位置用 0 來補(bǔ)足。最終我們得到一串二進(jìn)制數(shù)據(jù)11100101 10111111 10000011,即1110 0101 1011 1111 1000 0011,即\xe5\xbf\x83。
3、encode與decode的選擇
我們要把unicode符號(hào)保存到文件或者傳輸?shù)骄W(wǎng)絡(luò),此時(shí)需要進(jìn)行編碼將其轉(zhuǎn)換成str類型,于是Python提供了encode方法,反之,則需要進(jìn)行decode。
encode
>>> u = u'心'
>>> u
u'\u5fc3'
>>> u.encode('utf-8')
'\xe5\xbf\x83'
>>>
decode
>>> s = '心'
>>> s
'\xe5\xbf\x83'
>>> s.decode('utf-8')
u'\u5fc3'
>>>
在實(shí)驗(yàn)環(huán)境為UTF-8的條件下,字符串s默認(rèn)用UTF-8進(jìn)行編碼,即s的字節(jié)碼和u.encode('utf-8')的結(jié)果一致。
因此,我們可以得到下圖。
總之,還是那句話,編碼(encode)就是把字符(符號(hào))轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)的過程,因此unicode到str的轉(zhuǎn)換要用encode方法,反過來就是用decode方法。
encoding always takes a Unicode string and returns a bytes sequence, and decoding always takes a bytes sequence and returns a Unicode string.
另外,由第一節(jié)字符與字節(jié)的關(guān)系我們知道,Unicode編碼可以表示所有字符集,包括ASCII和GBK。因此,在使用encode/decode的時(shí)候一定要對應(yīng)上相應(yīng)的字符集,不然將可能會(huì)出現(xiàn)UnicodeEncodeError和UnicodeDecodeError。
三、UnicodeEncodeError和UnicodeDecodeError原因舉例分析
UnicodeEncodeError
主要發(fā)生在unicode字符串轉(zhuǎn)換成str字節(jié)序列的時(shí)候。例如,
>>> u = u'心'
>>> u.decode('gbk')
錯(cuò)誤日志
UnicodeEncodeError: 'ascii' codec can't encode character u'\u5fc3' in position 0: ordinal not in range(128)
為什么會(huì)出現(xiàn)UnicodeEncodeError呢?
因?yàn)?strong>decode是從二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成符號(hào)的過程,因此,unicode類型字符串需要先轉(zhuǎn)換成二進(jìn)制數(shù)據(jù),隱式調(diào)用了encode方法,并且使用了Python默認(rèn)的ascii碼來編碼。而ascii字符集只包含了128個(gè)字符,不包括中文字符,因此出現(xiàn)了上述錯(cuò)誤。
要解決該錯(cuò)誤,則必須指定正確的編碼規(guī)則來使用encode方法,如GBK。
>>> u = u'心'
>>> u.encode('gbk')
'\xd0\xc4'
>>> u.encode('gbk').decode('gbk')
u'\u5fc3'
>>>
其中
'\xd0\xc4'是“心”字在GBK字符集中的編碼(也可以理解為編號(hào)ID)。
UnicodeDecodeError
主要發(fā)生在str類型字節(jié)序列轉(zhuǎn)換成unicode類型字符串的時(shí)候。例如,
>>> s = '心'
>>> s.encode('gbk')
錯(cuò)誤日志
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
為什么會(huì)出現(xiàn)UnicodeDecodeError呢?
因?yàn)?strong>encode是從符號(hào)轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)的過程,因此str類型字節(jié)序列需要先轉(zhuǎn)換成符號(hào),隱式調(diào)用了decode方法,并且使用了Python默認(rèn)的ascii碼來解碼。而ascii字符集只包含了128個(gè)字符,不包括中文字符,因此出現(xiàn)了上述錯(cuò)誤。如果用GBK來解碼是否可以呢?比如直接執(zhí)行s.decode('gbk'),答案是不可以。
要解決該錯(cuò)誤,則必須指定正確的編碼規(guī)則來使用decode方法,如UTF-8。
>>> s = '心'
>>> s.decode('utf-8').encode('gbk')
'\xd0\xc4'
>>>
參考https://blog.csdn.net/trochiluses/article/details/16825269。
而如果涉及到UTF-8和GBK編碼的相互轉(zhuǎn)換,則只需遵循下圖即可做到正確轉(zhuǎn)換。本質(zhì)上是借助了字符是不變的,而Unicode字符集中包括了GBK的所有字符這一基本事實(shí)。
為了避免出現(xiàn)UnicodeEncodeError/UnicodeDecodeError或者亂碼的問題,encode和decode方法一定要成對使用,并且采用正確的字符集和編解碼規(guī)則。 最佳實(shí)踐是,建議采用”三明治“處理思想,僅在”出入口“進(jìn)行encode/decode操作。
簡單總結(jié)一下,本文粗略介紹了一下Unicode、GBK、ASCII以及UTF-8之間的關(guān)系;明確了字符與字節(jié)的關(guān)系;舉例介紹了Encode/Decode使用時(shí)的注意事項(xiàng);并舉例分析了UnicodeEncodeError/UnicodeDecodeError產(chǎn)生的原因;總結(jié)了一些經(jīng)驗(yàn)規(guī)則;然而,最重要還是文中的三張圖。