徹底解決UnicodeEncodeError與UnicodeDecodeError

字符與字節(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-8UTF-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)方式而已。

參考Unicode 和 UTF-8 有什么區(qū)別? - 盛世唐朝的回答 - 知乎

如果用一幅圖來表示的話,大概長成下面這樣子:

字符與字節(jié)的關(guā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é)果一致。
因此,我們可以得到下圖。

image

總之,還是那句話,編碼(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í)。

UTF-8編碼和GBK編碼的相互轉(zhuǎn)換

為了避免出現(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ī)則;然而,最重要還是文中的三張圖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 字符集和編碼簡介 在編程中常??梢砸姷礁鞣N字符集和編碼,包括ASCII,MBCS,Unicode等字符集。確切的說...
    蘭山小亭閱讀 9,077評論 0 13
  • 在人機(jī)交互之字符編碼 一文中對字符編碼進(jìn)行了詳細(xì)的討論,并通過一些簡單的小程序驗(yàn)證了我們對于字符編碼的認(rèn)識(shí)。但僅了...
    selfboot閱讀 2,486評論 0 2
  • 字符是用戶可以讀寫的最小單位。計(jì)算機(jī)所能支持的字符組成的集合,就叫做字符集。字符集通常以二維表的形式存在。二維表的...
    劉惜有閱讀 8,367評論 2 14
  • 前言 很多程序員對字符編碼不太理解,當(dāng)然平時(shí)接觸的也不是很多??赡苤皇谴蟾胖?ASCII、UTF8、GBK、Un...
    ZhengYaWei閱讀 1,484評論 0 7
  • 每次去圖書館也沒有發(fā)現(xiàn)什么,今天去圖書館發(fā)現(xiàn)有很多神秘的人在圖書館里面轉(zhuǎn)圈。手里還拿著很多紙,還一直問旁邊的人。...
    葉隨陽光閱讀 153評論 0 0

友情鏈接更多精彩內(nèi)容