自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一籌莫展?來看看字符編碼的前世今生吧

開發(fā) 后端
這篇文章從字符編碼的起源開始,講述了編程中應(yīng)該如何應(yīng)對(duì)編碼的問題,通過理解本文,你可以從容地定位、分析、解決字符編碼相關(guān)的問題。

前言

很多程序員對(duì)字符編碼不太理解,雖然他們大概知道 ASCII、UTF8、GBK、Unicode 等術(shù)語概念,但在寫代碼過程中還是會(huì)遇到各種奇怪的編碼問題,在 Java 中最常見的是亂碼,而 Python 開發(fā)中遇到最多的是編碼錯(cuò)誤,如:UnicodeDecodeError、UnicodeEncodeError,幾乎每個(gè) Python 開發(fā)者都會(huì)碰到這種問題,對(duì)此都是一籌莫展,這篇文章從字符編碼的起源開始,講述了編程中應(yīng)該如何應(yīng)對(duì)編碼的問題,通過理解本文,你可以從容地定位、分析、解決字符編碼相關(guān)的問題。

說到「字符編碼」我們先要理解什么是編碼以及為什么要編碼。

什么是編碼

但凡學(xué)過計(jì)算機(jī)的同學(xué)都知道,計(jì)算機(jī)只能處理0和1組成的二進(jìn)制數(shù)據(jù),人類借助計(jì)算機(jī)所看到的、聽到的任何信息,包括:文本、視頻、音頻、圖片在計(jì)算機(jī)中都是以二進(jìn)制形式進(jìn)行存儲(chǔ)和運(yùn)算。計(jì)算機(jī)善于處理二進(jìn)制數(shù)據(jù),但是人類對(duì)于二進(jìn)制數(shù)據(jù)顯得捉襟見肘,為了降低人與計(jì)算機(jī)的交流成本,人們決定把每個(gè)字符進(jìn)行編號(hào),比如給字母 A 的編號(hào)是 65,對(duì)應(yīng)的二進(jìn)制數(shù)是「01000001」,當(dāng)把 A 存到計(jì)算機(jī)中時(shí)就用 01000001 來代替,當(dāng)要加載顯示在文件中或網(wǎng)頁中用來閱覽時(shí),就把二進(jìn)制數(shù)轉(zhuǎn)換成字符 A,這個(gè)過程中就會(huì)涉及到不同格式數(shù)據(jù)之間的轉(zhuǎn)換。

編碼(encode)是把數(shù)據(jù)從一種形式轉(zhuǎn)換為另外一種形式的過程,它是一套算法,比如這里的字符 A 轉(zhuǎn)換成 01000001 就是一次編碼的過程,解碼(decode)就是編碼的逆過程。今天我們討論的是關(guān)于字符的編碼,是字符和二進(jìn)制數(shù)據(jù)之間轉(zhuǎn)換的算法。密碼學(xué)中的加密解密有時(shí)也稱為編碼與解碼,不過它不在本文討論范圍內(nèi)。

一籌莫展?來看看字符編碼的前世今生吧

什么是字符集

字符集是一個(gè)系統(tǒng)支持的所有抽象字符的集合。它是各種文字和符號(hào)的總稱,常見的字符集種類包括 ASCII 字符集、GBK 字符集、Unicode字符集等。不同的字符集規(guī)定了有限個(gè)字符,比如:ASCII 字符集只含有拉丁文字字母,GBK 包含了漢字,而 Unicode 字符集包含了世界上所有的文字符號(hào)。

有人不禁要問,字符集與字符編碼是什么關(guān)系?別急,先往下面

ASCII:字符集與字符編碼的起源

世界上第一臺(tái)計(jì)算機(jī),1945年由美國賓夕法尼亞大學(xué)的兩位教授-莫奇利和??颂卦O(shè)計(jì)和研制出來,美國人起草了計(jì)算機(jī)的第一份字符集和編碼標(biāo)準(zhǔn),叫 ASCII(American Standard Code for Information Interchange,美國信息交換標(biāo)準(zhǔn)代碼),一共規(guī)定了 128 個(gè)字符及對(duì)應(yīng)的二進(jìn)制轉(zhuǎn)換關(guān)系,128 個(gè)字符包括了可顯示的26個(gè)字母(大小寫)、10個(gè)數(shù)字、標(biāo)點(diǎn)符號(hào)以及特殊的控制符,也就是英語與西歐語言中常見的字符,這128個(gè)字符用一個(gè)字節(jié)來表示綽綽有余,因?yàn)橐粋€(gè)字節(jié)可以表示256個(gè)字符,所以當(dāng)前只利用了字節(jié)的7位,最高位用來當(dāng)作奇偶校驗(yàn)。如下圖所以,字符小寫 a 對(duì)應(yīng) 01100001,大寫 A 對(duì)應(yīng) 01000001。

一籌莫展?來看看字符編碼的前世今生吧

ASCII 字符集是字母、數(shù)字、標(biāo)點(diǎn)符號(hào)以及控制符(回車、換行、退格)等組成的128個(gè)字符。ASCII 字符編碼是將這128個(gè)字符轉(zhuǎn)換為計(jì)算機(jī)可識(shí)別的二進(jìn)制數(shù)據(jù)的一套規(guī)則(算法)?,F(xiàn)在可以回答前面的那個(gè)問題了,通常來說,字符集同時(shí)定義了一套同名的字符編碼規(guī)則,例如 ASCII 就定義了字符集以及字符編碼,當(dāng)然這不是絕對(duì)的,比如 Unicode 就只定義了字符集,而對(duì)應(yīng)的字符編碼是 UTF-8,UTF-16。

ASCII 由美國國家標(biāo)準(zhǔn)學(xué)會(huì)制定,1967年定案,最初是美國國家標(biāo)準(zhǔn),后來被國際標(biāo)準(zhǔn)化組織(International Organization for Standardization, ISO)定為國際標(biāo)準(zhǔn),稱為ISO 646標(biāo)準(zhǔn),適用于所有拉丁文字字母。

EASCII:擴(kuò)展的ASCII

隨著計(jì)算機(jī)的不斷普及,計(jì)算機(jī)開始被西歐等國家使用,然后西歐語言中還有很多字符不在 ASCII 字符集中,這給他們使用計(jì)算機(jī)造成了很大的限制,就好比在中國,你只能用英語跟人家交流一樣。于是乎,他們想著法子把 ASCII 字符集進(jìn)行擴(kuò)充,以為 ASCII 只使用了字節(jié)的前 7 位,如果把第八位也利用起來,那么可表示的字符個(gè)數(shù)就是 256。這就是后來的 EASCII(Extended ASCII,延伸美國標(biāo)準(zhǔn)信息交換碼)EASCII 碼比 ASCII 碼擴(kuò)充出來的符號(hào)包括表格符號(hào)、計(jì)算符號(hào)、希臘字母和特殊的拉丁符號(hào)。

然后 EASCII 并沒有形成統(tǒng)一的標(biāo)準(zhǔn),各國個(gè)商家都有自己的小算盤,都想在字節(jié)的高位做文章,比如 MS-DOS, IBM PC上使用了各自定義的編碼字符集,為了結(jié)束這種混亂的局面,國際標(biāo)準(zhǔn)化組織(ISO)及國際電工委員會(huì)(IEC)聯(lián)合制定的一系列8位元字符集的標(biāo)準(zhǔn),叫 ISO 8859,全稱ISO/IEC 8859,它在 ASCII 基礎(chǔ)之上擴(kuò)展而來,所以完全 ASCII,ISO 8859 字符編碼方案所擴(kuò)展的這128個(gè)編碼中,只有0xA0~0xFF(十進(jìn)制為160~255)被使用,其實(shí) ISO 8859是一組字符集的總稱,旗下共包含了15個(gè)字符集,分別是 ISO 8859-1 ~ ISO 8859-15,ISO 8859-1 又稱之為 Latin-1,它是西歐語言,其它的分別代表 中歐、南歐、北歐等字符集。

一籌莫展?來看看字符編碼的前世今生吧

GB2312:滿足國人需求的字符集

后來,計(jì)算機(jī)開始普及到了中國,但面臨的一個(gè)問題就是字符,漢字博大精深,常用漢字有3500個(gè),已經(jīng)大大超出了 ASCII 字符集所能表示的字符范圍了,即使是 EASCII 也顯得杯水車薪,1981 年國家標(biāo)準(zhǔn)化管理委員會(huì)定了一套字符集叫 GB2312,每個(gè)漢字符號(hào)由兩個(gè)字節(jié)組成,理論上它可以表示65536個(gè)字符,不過它只收錄了7445個(gè)字符,6763個(gè)漢字和682個(gè)其他字符,同時(shí)它能夠兼容 ASCII,ASCII 中定義的字符只占用一個(gè)字節(jié)的空間。

GB2312 所收錄的漢字已經(jīng)覆蓋中國大陸99.75%的使用頻率,但是對(duì)一些罕見的字和繁體字還有很多少數(shù)民族使用的字符都沒法處理,于是后來就在 GB2312 的基礎(chǔ)上創(chuàng)建了一種叫 GBK 的字符編碼,GBK 不僅收錄了27484 個(gè)漢字,同時(shí)還收錄了藏文、蒙文、維吾爾文等主要的少數(shù)民族文字。GBK 是利用了 GB2312 中未被使用的編碼空間上進(jìn)行擴(kuò)充,所以它能完全兼容 GB2312和 ASCII。而 GB 18030 是現(xiàn)時(shí)最新的字符集,兼容 GB 2312-1980 和 GBK, 共收錄漢字70244個(gè),采用多字節(jié)編碼,每個(gè)字符可以有1、2、4個(gè)字節(jié)組成,某種意義上它能容納161 萬個(gè)字符,包含繁體漢字以及日韓漢字,單字節(jié)與ASCII兼容,雙字節(jié)與GBK標(biāo)準(zhǔn)兼容。

Unicode :統(tǒng)一江湖的字符集

盡管我們有了屬于自己的字符集和字符編碼 GBK,可世界上還有很多國家擁有自己的語言和文字,比如日本用 JIS,臺(tái)灣用 BIG5,不同國家之間交流起來就很困難,因?yàn)闆]有統(tǒng)一的編碼標(biāo)準(zhǔn),可能同一個(gè)字符,在A國家用兩字字節(jié)存儲(chǔ),而到了B國家是3個(gè)字節(jié),這樣很容易出現(xiàn)編碼問題,于是在 1991 年,國際標(biāo)準(zhǔn)化組織和統(tǒng)一碼聯(lián)盟組織各自開發(fā)了 ISO/IEC 10646(USC)和 Unicode 項(xiàng)目,這兩個(gè)項(xiàng)目的目的都是希望用一種字符集來統(tǒng)一全世界所有字符,不過很快雙方都意識(shí)到世界上并不需要兩個(gè)不兼容的字符集。于是他們就編碼問題進(jìn)行了非常友好地會(huì)晤,決定彼此把工作內(nèi)容合并,雖然項(xiàng)目還是獨(dú)立存在,各自發(fā)布各自的標(biāo)準(zhǔn),但前提是兩者必須保持兼容。不過由于 Unicode 這一名字比較好記,因而它使用更為廣泛,成為了事實(shí)上的統(tǒng)一編碼標(biāo)準(zhǔn)。

以上是對(duì)字符集歷史的一個(gè)簡要回顧,現(xiàn)在重點(diǎn)來說說Unicode,Unicode 是一個(gè)囊括了世界上所有字符的字符集,其中每一個(gè)字符都對(duì)應(yīng)有唯一的編碼值(code point),注意了!它不是字符編碼,僅僅是字符集而已,Unicode 字符如何進(jìn)行編碼,可以是 UTF-8、UTF-16、甚至用 GBK 來編碼。例如:

 

  1. >>> a = u"好" 
  2. >>> a 
  3. u'\u597d' 
  4. >>> b = a.encode("utf-8"
  5. >>> b 
  6. '\xe5\xa5\xbd' 
  7. >>> 
  8.  
  9. >>> b = a.encode("gbk"
  10. >>> b 
  11. '\xba\xc3' 

Unicode 本身并沒有規(guī)定一個(gè)字符究竟是用一個(gè)還是三個(gè)或者四個(gè)字節(jié)表示。Unicode 只規(guī)定了每個(gè)字符對(duì)應(yīng)到唯一的代碼值(code point),代碼值 從 0000 ~ 10FFFF 共 1114112 個(gè)值 ,真正存儲(chǔ)的時(shí)候需要多少個(gè)字節(jié)是由具體的編碼格式?jīng)Q定的。比如:字符 「A」用 UTF-8 的格式編碼來存儲(chǔ)就只占用1個(gè)字節(jié),用 UTF-16 就占用2個(gè)字節(jié),而用 UTF-32 存儲(chǔ)就占用4個(gè)字節(jié)。

UTF-8:Unicode編碼

UTF( Unicode Transformation Format)編碼 和 USC(Universal Coded Character Set) 編碼分別是 Unicode 、ISO/IEC 10646 編碼體系里面兩種編碼方式,UCS 分為 UCS-2 和 UCS-4,而 UTF 常見的種類有 UTF-8、UTF-16、UTF-32。因?yàn)?Unicode 與 USC 兩種字符集是相互兼容的,所以這幾種編碼格式也有著對(duì)應(yīng)的等值關(guān)系

UCS-2 使用兩個(gè)定長的字節(jié)來表示一個(gè)字符,UTF-16 也是使用兩個(gè)字節(jié),不過 UTF-16 是變長的(網(wǎng)上很多錯(cuò)誤的說法說 UTF-16是定長的),遇到兩個(gè)字節(jié)沒法表示時(shí),會(huì)用4個(gè)字節(jié)來表示,因此 UTF-16 可以看作是在 UCS-2 的基礎(chǔ)上擴(kuò)展而來的。而 UTF-32 與 USC-4 是完全等價(jià)的,使用4個(gè)字節(jié)表示,顯然,這種方式浪費(fèi)的空間比較多。

UTF-8 的優(yōu)勢(shì)是:它以單字節(jié)為單位用 1~4 個(gè)字節(jié)來表示一個(gè)字符,從首字節(jié)就可以判斷一個(gè)字符的UTF-8編碼有幾個(gè)字節(jié)。如果首字節(jié)以0開頭,肯定是單字節(jié)編碼,如果以110開頭,肯定是雙字節(jié)編碼,如果是1110開頭,肯定是三字節(jié)編碼,以此類推。除了單字節(jié)外,多字節(jié)UTF-8碼的后續(xù)字節(jié)均以10開頭。

1~4 字節(jié)的 UTF-8 編碼看起來是這樣的:

引用

 

  1. 0xxxxxxx  
  2. 110xxxxx 10xxxxxx  
  3. 1110xxxx 10xxxxxx 10xxxxxx  
  4. 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
  • 單字節(jié)可編碼的 Unicode 范圍:\u0000~\u007F(0~127)
  • 雙字節(jié)可編碼的 Unicode 范圍:\u0080~\u07FF(128~2047)
  • 三字節(jié)可編碼的 Unicode 范圍:\u0800~\uFFFF(2048~65535)
  • 四字節(jié)可編碼的 Unicode 范圍:\u10000~\u1FFFFF(65536~2097151)

UTF-8 兼容了 ASCII,在數(shù)據(jù)傳輸和存儲(chǔ)過程中節(jié)省了空間,其二是UTF-8 不需要考慮大小端問題。這兩點(diǎn)都是 UTF-16 的劣勢(shì)。不過對(duì)于中文字符,用 UTF-8 就要用3個(gè)字節(jié),而 UTF-16 只需2個(gè)字節(jié)。而UTF-16 的優(yōu)點(diǎn)是在計(jì)算字符串長度,執(zhí)行索引操作時(shí)速度會(huì)很快。Java 內(nèi)部使用 UTF-16 編碼方案。而 Python3 使用 UTF-8。UTF-8 編碼在互聯(lián)網(wǎng)領(lǐng)域應(yīng)用更加廣泛。

來看一張圖,下圖是Windows平臺(tái)保存文件時(shí)可選擇的字符編碼類型,你可以指定系統(tǒng)以什么樣的編碼格式來存儲(chǔ)文件,ANSI 是 ISO 8859-1的超集,之所以在 Windows下有 Unicode 編碼這樣一種說法,其實(shí)是 Windows 的一種錯(cuò)誤表示方法,或許是因?yàn)闅v史原因一直沿用至今,其實(shí)它真正表示的是 UTF-16 編碼,更具體一點(diǎn)是 UTF-16小端,什么是大端和小端呢?

一籌莫展?來看看字符編碼的前世今生吧

大端與小端

大小端是數(shù)據(jù)在存儲(chǔ)器中的存放順序,大端模式,是指數(shù)據(jù)的高字節(jié)在前,保存在內(nèi)存的低地址中,與人類的讀寫法一致,數(shù)據(jù)的低字節(jié)在后,保存在內(nèi)存的高地址中,小端與之相反,小端模式,是指數(shù)據(jù)的高字節(jié)在后,保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)在前,保存在內(nèi)存的低地址中例如,十六進(jìn)制數(shù)值 0x1234567 的大端字節(jié)序和小端字節(jié)序的寫法:

一籌莫展?來看看字符編碼的前世今生吧

至于為什么會(huì)有大端和小端之分呢?對(duì)于 16 位或者 32 位的處理器,由于寄存器寬度大于一個(gè)字節(jié),那么必然存在著一個(gè)如何將多個(gè)字節(jié)排放的問題,因?yàn)椴煌僮飨到y(tǒng)讀取多字節(jié)的順序不一樣,,x86和一般的OS(如windows,F(xiàn)reeBSD,Linux)使用的是小端模式。但比如Mac OS是大端模式。因此就導(dǎo)致了大端存儲(chǔ)模式和小端存儲(chǔ)模式的存在,兩者并沒有孰優(yōu)孰劣。

為什么UTF-8不需要考慮大小端問題?

UTF-8 的編碼單元是1個(gè)字節(jié),所以就不用考慮字節(jié)序問題。而 UTF-16 是用 2個(gè)字節(jié)來編碼 Unicode 字符,編碼單位是兩個(gè)字節(jié),因此需要考慮字節(jié)序問題,因?yàn)?個(gè)字節(jié)哪個(gè)存高位哪個(gè)存低位需要確定。

Python2 中的字符編碼

現(xiàn)在總算把理論說完了,再來說說 Python 中的編碼問題,也是每個(gè)Python開發(fā)者最關(guān)心、最經(jīng)常遇到的問題,Python 的誕生時(shí)間比 Unicode 還要早幾年,所以,Python的第一個(gè)版本一直延續(xù)到Python2.7,Python 的默認(rèn)編碼都是 ASCII

 

  1. >>> import sys 
  2. >>> sys.getdefaultencoding() 
  3. 'ascii' 

所以在 Python 源代碼,要能夠正常保存中文字符就必須先指定utf

8 或者 gbk 格式

  1. # coding=utf-8 

或者是:

  1. #!/usr/bin/python 
  2. # -*- coding: utf-8 -*- 

在前面我們介紹過字符,這里還有必要重復(fù)一下字符和字節(jié)的區(qū)別,字符就是一個(gè)符號(hào),比如一個(gè)漢字、一個(gè)字母、一個(gè)數(shù)字、一個(gè)標(biāo)點(diǎn)都可以稱為一個(gè)字符,而字節(jié)就是字符就是編碼之后轉(zhuǎn)換而成的二進(jìn)制序列,一個(gè)字節(jié)是8個(gè)比特位。例如字符 "p" 存儲(chǔ)到硬盤是一串二進(jìn)制數(shù)據(jù) 01110000,占用一個(gè)字節(jié)。字節(jié)方便存儲(chǔ)和網(wǎng)絡(luò)傳輸,而字符用于顯示方便閱讀。

在Python2中,字符與字節(jié)的表示很微妙,兩者的界限很模糊,Python2 中把字符串分為 unicode 和 str 兩種類型。本質(zhì)上 str 類型是二進(jìn)制字節(jié)序列, unicode 類型的字符串是字符,下面的示例代碼可以看出 str 類型的 "禪" 打印出來是十六進(jìn)制的 \xec\xf8 ,對(duì)應(yīng)的二進(jìn)制字節(jié)序列就是 '11101100 11111000'。

 

  1. >>> s = '禪' 
  2. >>> s 
  3. '\xec\xf8' 
  4. >>> type(s) 
  5. <type 'str'

而 unicode 類型的 u"禪" 對(duì)應(yīng)的 unicode 符號(hào)是 u'\u7985'

 

  1. >>> u = u"禪" 
  2. >>> u 
  3. u'\u7985' 
  4. >>> type(u) 
  5. <type 'unicode'

我們要把 unicode 字符保存到文件或者傳輸?shù)骄W(wǎng)絡(luò)就需要經(jīng)過編碼處理轉(zhuǎn)換成二進(jìn)制形式的 str 類型,于是 python 的字符串提供了 encode 方法,從 unicode 轉(zhuǎn)換到 str,反之亦然。

一籌莫展?來看看字符編碼的前世今生吧

  1. >>> u = u"禪" 
  2. >>> u 
  3. u'\u7985' 
  4. >>> u.encode("utf-8"
  5. '\xe7\xa6\x85' 
  6.  
  7. >>> s = "禪" 
  8. >>> s.decode("utf-8"
  9. u'\u7985' 
  10. >>> 

不少初學(xué)者怎么也記不住 str 與 unicode 之間的轉(zhuǎn)換用 encode 還是 decode,如果你記住了 str 本質(zhì)上其實(shí)是一串二進(jìn)制數(shù)據(jù),而 unicode 是字符(符號(hào)),編碼(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".

清楚了 str 與 unicode 之間的轉(zhuǎn)換關(guān)系之后,我們來看看什么時(shí)候會(huì)出現(xiàn) UnicodeEncodeError、UnicodeDecodeError 錯(cuò)誤。

UnicodeEncodeError

UnicodeEncodeError 發(fā)生在 unicode 字符串轉(zhuǎn)換成 str 字節(jié)序列的時(shí)候,來看一個(gè)例子,把一串 unicode 字符串保存到文件

 

  1. # -*- coding:utf-8 -*- 
  2. def main(): 
  3.     name = u'Python之禪' 
  4.     f = open("output.txt""w"
  5.     f.write(name

錯(cuò)誤日志

引用

  • UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-7: ordinal not in range(128)

為什么會(huì)出現(xiàn) UnicodeEncodeError?

因?yàn)檎{(diào)用 write 方法時(shí),程序會(huì)把字符經(jīng)過編碼轉(zhuǎn)換成二進(jìn)制字節(jié)序列,內(nèi)部會(huì)有 unicode 到 str 的編碼轉(zhuǎn)換過程,程序會(huì)先判斷字符串是什么類型,如果是 str,就直接寫入文件,不需要編碼,因?yàn)?str 類型的字符串本身就是一串二進(jìn)制的字節(jié)序列了。如果字符串是 unicode 類型,那么它會(huì)先調(diào)用 encode 方法把 unicode 字符串轉(zhuǎn)換成二進(jìn)制形式的 str 類型,才保存到文件,而 Python2中,encode 方法默認(rèn)使用 ascii 進(jìn)行 encde.

相當(dāng)于:

  1. >>> u"Python之禪".encode("ascii"

但是,我們知道 ASCII 字符集中只包含了128個(gè)拉丁字母,不包括中文字符,因此 出現(xiàn)了 'ascii' codec can't encode characters 的錯(cuò)誤。要正確地使用 encode ,就必須指定一個(gè)包含了中文字符的字符集,比如:UTF-8、GBK。

 

  1. >>> u"Python之禪".encode("utf-8"
  2. 'Python\xe4\xb9\x8b\xe7\xa6\x85' 
  3.  
  4. >>> u"Python之禪".encode("gbk"
  5. 'Python\xd6\xae\xec\xf8' 

所以要把 unicode 字符串正確地寫入文件,就應(yīng)該預(yù)先把字符串進(jìn)行 UTF-8 或 GBK 編碼轉(zhuǎn)換。

 

  1. def main(): 
  2.     name = u'Python之禪' 
  3.     name = name.encode('utf-8'
  4.     with open("output.txt""w"as f: 
  5.         f.write(name

或者直接寫str類型的字符串

 

  1. def main(): 
  2.     name = 'Python之禪' 
  3.     with open("output.txt""w"as f: 
  4.         f.write(name

當(dāng)然,把 unicode 字符串正確地寫入文件不止一種方式,但原理是一樣的,這里不再介紹,把字符串寫入數(shù)據(jù)庫,傳輸?shù)骄W(wǎng)絡(luò)都是同樣的原理

UnicodeDecodeError

UnicodeDecodeError 發(fā)生在 str 類型的字節(jié)序列解碼成 unicode 類型的字符串時(shí)

 

  1. >>> a = u"禪" 
  2. >>> a 
  3. u'\u7985' 
  4. >>> b = a.encode("utf-8"
  5. >>> b 
  6. '\xe7\xa6\x85' 
  7. >>> b.decode("gbk"
  8. Traceback (most recent call last): 
  9.   File "<stdin>", line 1, in <module> 
  10. UnicodeDecodeError: 'gbk' codec can't decode byte 0x85 in position 2: incomplete multibyte sequence 

把一個(gè)經(jīng)過 UTF-8 編碼后生成的字節(jié)序列 '\xe7\xa6\x85' 再用 GBK 解碼轉(zhuǎn)換成 unicode 字符串時(shí),出現(xiàn) UnicodeDecodeError,因?yàn)?(對(duì)于中文字符)GBK 編碼只占用兩個(gè)字節(jié),而 UTF-8 占用3個(gè)字節(jié),用 GBK 轉(zhuǎn)換時(shí),還多出一個(gè)字節(jié),因此它沒法解析。避免 UnicodeDecodeError 的關(guān)鍵是保持 編碼和解碼時(shí)用的編碼類型一致。

這也回答了文章開頭說的字符 "禪",保存到文件中有可能占3個(gè)字節(jié),有可能占2個(gè)字節(jié),具體處決于 encode 的時(shí)候指定的編碼格式是什么。

再舉一個(gè) UnicodeDecodeError 的例子

 

  1. >>> x = u"Python" 
  2. >>> y = "之禪" 
  3. >>> x + y 
  4. Traceback (most recent call last): 
  5.   File "<stdin>", line 1, in <module> 
  6. UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) 
  7. >>> 

str 與 unicode 字符串 執(zhí)行 + 操作時(shí),Python 會(huì)把 str 類型的字節(jié)序列隱式地轉(zhuǎn)換成(解碼)成 和 x 一樣的 unicode 類型,但Python是使用默認(rèn)的 ascii 編碼來轉(zhuǎn)換的,而 ASCII字符集中不包含有中文,所以報(bào)錯(cuò)了。相當(dāng)于:

 

  1. >>> y.decode('ascii'
  2. Traceback (most recent call last): 
  3.   File "<stdin>", line 1, in <module> 
  4. UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) 

正確地方式應(yīng)該是找到一種包含有中文字符的字符編碼,比如 UTF-8或者 GBK 顯示地把 y 進(jìn)行解碼轉(zhuǎn)換成 unicode 類型

 

  1. >>> x = u"Python" 
  2. >>> y = "之禪" 
  3. >>> y = y.decode("utf-8"
  4. >>> x + y 
  5. u'Python\u4e4b\u7985' 

Python3中的字符串與字節(jié)序列

Python3對(duì)字符串和字符編碼進(jìn)行了很徹底的重構(gòu),完全不兼容Python2,同時(shí)也很多想遷移到Python3的項(xiàng)目帶來了很大的麻煩,Python3 把系統(tǒng)默認(rèn)編碼設(shè)置為 UTF-8,字符和二進(jìn)制字節(jié)序列區(qū)分得更清晰,分別用 str 和 bytes 表示。文本字符全部用 str 類型表示,str 能表示 Unicode 字符集中所有字符,而二進(jìn)制字節(jié)數(shù)據(jù)用一種全新的數(shù)據(jù)類型,用 bytes 來表示,盡管Python2中也有bytes類型,但那只不過是str的一個(gè)別名。

 

  1. >>> a = "a" 
  2. >>> a 
  3. 'a' 
  4. >>> type(a) 
  5. <class 'str'
  6.  
  7. >>> b = "禪" 
  8. >>> b 
  9. '禪' 
  10. >>> type(b) 
  11. <class 'str'

bytes

Python3 中,在字符引號(hào)前加‘b’,明確表示這是一個(gè) bytes 類型的對(duì)象,實(shí)際上它就是一組二進(jìn)制字節(jié)序列組成的數(shù)據(jù),bytes 類型可以是 ASCII范圍內(nèi)的字符和其它十六進(jìn)制形式的字符數(shù)據(jù),但不能用中文等非ASCII字符表示。

 

  1. >>> c = b'a' 
  2. >>> c 
  3. b'a' 
  4. >>> type(c) 
  5. <class 'bytes'
  6.  
  7. >>> d = b'\xe7\xa6\x85' 
  8. >>> d 
  9. b'\xe7\xa6\x85' 
  10. >>> type(d) 
  11. <class 'bytes'
  12. >>> 
  13.  
  14. >>> e = b'禪' 
  15.   File "<stdin>", line 1 
  16. SyntaxError: bytes can only contain ASCII literal characters. 

bytes 類型提供的操作和 str 一樣,支持分片、索引、基本數(shù)值運(yùn)算等操作。但是 str 與 bytes 類型的數(shù)據(jù)不能執(zhí)行 + 操作,盡管在python2中是可行的。

 

  1. >>> b"a"+b"c" 
  2. b'ac' 
  3. >>> b"a"*2 
  4. b'aa' 
  5. >>> b"abcdef\xd6"[1:] 
  6. b'bcdef\xd6' 
  7. >>> b"abcdef\xd6"[-1] 
  8. 214 
  9.  
  10. >>> b"a" + "b" 
  11. Traceback (most recent call last): 
  12.   File "<stdin>", line 1, in <module> 
  13. TypeError: can't concat bytes to str 

python2 與 python3 字節(jié)與字符對(duì)比

 

一籌莫展?來看看字符編碼的前世今生吧

 

總結(jié)

 

  • 字符編碼本質(zhì)上是字符到字節(jié)的轉(zhuǎn)換過程
  • 字符集的演進(jìn)過程是:ascii、eascii、ios8895-x,gb2312... Unicode
  • Unicode是字符集,對(duì)應(yīng)的編碼格式有UTF-8,UTF-16
  • 字節(jié)序列存儲(chǔ)的時(shí)候有大小端之分
  • python2中字符與字節(jié)分別用unicode和str類型表示
  • python3中字符與字節(jié)分別用str與bytes表示
責(zé)任編輯:未麗燕 來源: ITeye
相關(guān)推薦

2013-03-07 13:38:03

2015-01-04 10:06:07

2021-05-11 10:03:04

數(shù)據(jù)泄露漏洞信息安全

2018-07-01 16:00:20

2011-04-22 14:11:38

傳真機(jī)

2018-03-14 09:43:39

云存儲(chǔ)備份數(shù)據(jù)

2011-07-14 11:24:23

2019-05-22 17:34:16

代碼開發(fā)工具

2017-03-27 16:27:00

深度學(xué)習(xí)

2011-06-02 09:47:23

傳真機(jī)技巧

2011-08-23 09:52:31

CSS

2015-11-18 14:14:11

OPNFVNFV

2014-07-30 10:55:27

2025-02-12 11:25:39

2016-01-07 14:23:22

Linux發(fā)行版排行

2009-12-04 16:19:57

無線路由器選擇方法

2011-06-13 10:07:56

機(jī)房布線管理配線架配線架

2016-12-29 13:34:04

阿爾法狗圍棋計(jì)算機(jī)

2013-05-23 16:23:42

Windows Azu微軟公有云

2014-07-15 10:31:07

asyncawait
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)