http://mp.weixin.qq.com/s/KyCy5UI1Q8C2ky4iLP9Bow:html
好比給字母 A 的編號是 65,對應的二進制數是「01000001」,當把 A 存到計算機中時就用 01000001 來代替,當要加載顯示在文件中或網頁中用來閱覽時,就把二進制數轉換成字符 A,這個過程當中就會涉及到不一樣格式數據之間的轉換。python
編碼(encode)是把數據從一種形式轉換爲另一種形式的過程,它是一套算法,好比這裏的字符 A 轉換成 01000001 就是一次編碼的過程,解碼(decode)就是編碼的逆過程。今天咱們討論的是關於字符的編碼,是字符和二進制數據之間轉換的算法。密碼學中的加密解密有時也稱爲編碼與解碼,不過它不在本文討論範圍內。linux
字符集:ios
字符集是一個系統支持的全部抽象字符的集合。它是各類文字和符號的總稱,常見的字符集種類包括 ASCII 字符集、GBK 字符集、Unicode字符集等。不一樣的字符集規定了有限個字符,好比:ASCII 字符集只含有拉丁文字字母,GBK 包含了漢字,而 Unicode 字符集包含了世界上全部的文字符號。算法
ASCII:字符集與字符編碼的起源
世界上第一臺計算機,1945年由美國賓夕法尼亞大學的兩位教授-莫奇利和埃克特設計和研製出來,美國人起草了計算機的第一份字符集和編碼標準,叫 ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼),一共規定了 128 個字符及對應的二進制轉換關係,128 個字符包括了可顯示的26個字母(大小寫)、10個數字、標點符號以及特殊的控制符,也就是英語與西歐語言中常見的字符,這128個字符用一個字節來表示綽綽有餘,由於一個字節能夠表示256個字符,因此當前只利用了字節的7位,最高位用來看成奇偶校驗。以下圖因此,字符小寫 a 對應 01100001,大寫 A 對應 01000001。數據庫
ASCII 字符集是字母、數字、標點符號以及控制符(回車、換行、退格)等組成的128個字符。ASCII 字符編碼是將這128個字符轉換爲計算機可識別的二進制數據的一套規則(算法)。如今能夠回答前面的那個問題了,一般來講,字符集同時定義了一套同名的字符編碼規則,例如 ASCII 就定義了字符集以及字符編碼,固然這不是絕對的,好比 Unicode 就只定義了字符集,而對應的字符編碼是 UTF-8,UTF-16。windows
ASCII 由美國國家標準學會制定,1967年定案,最初是美國國家標準,後來被國際標準化組織(International Organization for Standardization, ISO)定爲國際標準,稱爲ISO 646標準,適用於全部拉丁文字字母。網絡
EASCII:擴展的ASCII
隨着計算機的不斷普及,計算機開始被西歐等國家使用,而後西歐語言中還有不少字符不在 ASCII 字符集中,這給他們使用計算機形成了很大的限制,就比如在中國,你只能用英語跟人家交流同樣。因而乎,他們想着法子把 ASCII 字符集進行擴充,覺得 ASCII 只使用了字節的前 7 位,若是把第八位也利用起來,那麼可表示的字符個數就是 256。這就是後來的 EASCII(Extended ASCII,延伸美國標準信息交換碼)EASCII 碼比 ASCII 碼擴充出來的符號包括表格符號、計算符號、希臘字母和特殊的拉丁符號。編碼
而後 EASCII 並無造成統一的標準,各國個商家都有本身的小算盤,都想在字節的高位作文章,好比 MS-DOS, IBM PC上使用了各自定義的編碼字符集,爲告終束這種混亂的局面,國際標準化組織(ISO)及國際電工委員會(IEC)聯合制定的一系列8位元字符集的標準,叫 ISO 8859,全稱ISO/IEC 8859,它在 ASCII 基礎之上擴展而來,因此徹底 ASCII,ISO 8859 字符編碼方案所擴展的這128個編碼中,只有0xA0~0xFF(十進制爲160~255)被使用,其實 ISO 8859是一組字符集的總稱,旗下共包含了15個字符集,分別是 ISO 8859-1 ~ ISO 8859-15,ISO 8859-1 又稱之爲 Latin-1,它是西歐語言,其它的分別表明 中歐、南歐、北歐等字符集。加密
GB2312:知足國人需求的字符集
後來,計算機開始普及到了中國,但面臨的一個問題就是字符,漢字博大精深,經常使用漢字有3500個,已經大大超出了 ASCII 字符集所能表示的字符範圍了,即便是 EASCII 也顯得杯水車薪,1981 年國家標準化管理委員會定了一套字符集叫GB2312,每一個漢字符號由兩個字節組成,理論上它能夠表示65536個字符,不過它只收錄了7445個字符,6763個漢字和682個其餘字符,同時它可以兼容 ASCII,ASCII 中定義的字符只佔用一個字節的空間。
GB2312 所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率,可是對一些罕見的字和繁體字還有不少少數民族使用的字符都無法處理,因而後來就在 GB2312 的基礎上建立了一種叫 GBK 的字符編碼,GBK 不只收錄了27484 個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。GBK 是利用了 GB2312 中未被使用的編碼空間上進行擴充,因此它能徹底兼容 GB2312和 ASCII。而 GB 18030 是現時最新的字符集,兼容 GB 2312-1980 和 GBK, 共收錄漢字70244個,採用多字節編碼,每一個字符能夠有一、二、4個字節組成,某種意義上它能容納161 萬個字符,包含繁體漢字以及日韓漢字,單字節與ASCII兼容,雙字節與GBK標準兼容。
Unicode :統一江湖的字符集
儘管咱們有了屬於本身的字符集和字符編碼 GBK,可世界上還有不少國家擁有本身的語言和文字,好比日本用 JIS,臺灣用 BIG5,不一樣國家之間交流起來就很困難,由於沒有統一的編碼標準,可能同一個字符,在A國家用兩字字節存儲,而到了B國家是3個字節,這樣很容易出現編碼問題,因而在 1991 年,國際標準化組織和統一碼聯盟組織各自開發了 ISO/IEC 10646(USC)和 Unicode 項目,這兩個項目的目的都是但願用一種字符集來統一全世界全部字符,不過很快雙方都意識到世界上並不須要兩個不兼容的字符集。因而他們就編碼問題進行了很是友好地會晤,決定彼此把工做內容合併,雖然項目仍是獨立存在,各自發布各自的標準,但前提是二者必須保持兼容。不過因爲 Unicode 這一名字比較好記,於是它使用更爲普遍,成爲了事實上的統一編碼標準。
以上是對字符集歷史的一個簡要回顧,如今重點來講說Unicode,Unicode 是一個囊括了世界上全部字符的字符集,其中每個字符都對應有惟一的編碼值(code point),注意了!它不是字符編碼,僅僅是字符集而已,Unicode 字符如何進行編碼,能夠是 UTF-八、UTF-1六、甚至用 GBK 來編碼。例如:
>>> a = u"好">>> au'\u597d'>>> b = a.encode("utf-8")>>> b'\xe5\xa5\xbd'>>>>>> b = a.encode("gbk")>>> b'\xba\xc3'Unicode 自己並無規定一個字符到底是用一個仍是三個或者四個字節表示。Unicode 只規定了每一個字符對應到惟一的代碼值(code point),代碼值 從 0000 ~ 10FFFF 共 1114112 個值 ,真正存儲的時候須要多少個字節是由具體的編碼格式決定的。好比:字符 「A」用 UTF-8 的格式編碼來存儲就只佔用1個字節,用 UTF-16 就佔用2個字節,而用 UTF-32 存儲就佔用4個字節。
UTF-8:Unicode編碼
UTF( Unicode Transformation Format)編碼 和 USC(Universal Coded Character Set) 編碼分別是 Unicode 、ISO/IEC 10646 編碼體系裏面兩種編碼方式,UCS 分爲 UCS-2 和 UCS-4,而 UTF 常見的種類有 UTF-八、UTF-1六、UTF-32。由於 Unicode 與 USC 兩種字符集是相互兼容的,因此這幾種編碼格式也有着對應的等值關係。
UCS-2 使用兩個定長的字節來表示一個字符,UTF-16 也是使用兩個字節,不過 UTF-16 是變長的(網上不少錯誤的說法說 UTF-16是定長的),遇到兩個字節無法表示時,會用4個字節來表示,所以 UTF-16 能夠看做是在 UCS-2 的基礎上擴展而來的。而 UTF-32 與 USC-4 是徹底等價的,使用4個字節表示,顯然,這種方式浪費的空間比較多。
UTF-8 的優點是:它以單字節爲單位用 1~4 個字節來表示一個字符,從首字節就能夠判斷一個字符的UTF-8編碼有幾個字節。若是首字節以0開頭,確定是單字節編碼,若是以110開頭,確定是雙字節編碼,若是是1110開頭,確定是三字節編碼,以此類推。除了單字節外,多字節UTF-8碼的後續字節均以10開頭。
1~4 字節的 UTF-8 編碼看起來是這樣的:
0xxxxxxx110xxxxx 10xxxxxx1110xxxx 10xxxxxx 10xxxxxx11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
單字節可編碼的 Unicode 範圍:\u0000~\u007F(0~127)
雙字節可編碼的 Unicode 範圍:\u0080~\u07FF(128~2047)
三字節可編碼的 Unicode 範圍:\u0800~\uFFFF(2048~65535)
四字節可編碼的 Unicode 範圍:\u10000~\u1FFFFF(65536~2097151)
UTF-8 兼容了 ASCII,在數據傳輸和存儲過程當中節省了空間,其二是UTF-8 不須要考慮大小端問題。這兩點都是 UTF-16 的劣勢。不過對於中文字符,用 UTF-8 就要用3個字節,而 UTF-16 只需2個字節。而UTF-16 的優勢是在計算字符串長度,執行索引操做時速度會很快。Java 內部使用 UTF-16 編碼方案。而 Python3 使用 UTF-8。UTF-8 編碼在互聯網領域應用更加普遍。
來看一張圖,下圖是Windows平臺保存文件時可選擇的字符編碼類型,你能夠指定系統以什麼樣的編碼格式來存儲文件,ANSI 是 ISO 8859-1的超集,之因此在 Windows下有 Unicode 編碼這樣一種說法,實際上是 Windows 的一種錯誤表示方法,或許是由於歷史緣由一直沿用至今,其實它真正表示的是 UTF-16 編碼,更具體一點是 UTF-16小端,什麼是大端和小端呢?
大端與小端
大小端是數據在存儲器中的存放順序,大端模式,是指數據的高字節在前,保存在內存的低地址中,與人類的讀寫法一致,數據的低字節在後,保存在內存的高地址中,小端與之相反,小端模式,是指數據的高字節在後,保存在內存的高地址中,而數據的低字節在前,保存在內存的低地址中例如,十六進制數值
0x1234567
的大端字節序和小端字節序的寫法:
至於爲何會有大端和小端之分呢?對於 16 位或者 32 位的處理器,因爲寄存器寬度大於一個字節,那麼必然存在着一個如何將多個字節排放的問題,由於不一樣操做系統讀取多字節的順序不同,x86和通常的OS(如windows,FreeBSD,Linux)使用的是小端模式。但好比Mac OS是大端模式。所以就致使了大端存儲模式和小端存儲模式的存在,二者並無孰優孰劣。
爲何 UTF-8 不須要考慮大小端問題?
UTF-8 的編碼單元是1個字節,因此就不用考慮字節序問題。而 UTF-16 是用 2個字節來編碼 Unicode 字符,編碼單位是兩個字節,所以須要考慮字節序問題,由於2個字節哪一個存高位哪一個存低位須要肯定。
Python2 中的字符編碼
如今總算把理論說完了,再來講說 Python 中的編碼問題,也是每一個Python開發者最關心、最常常遇到的問題,Python 的誕生時間比 Unicode 還要早幾年,因此,Python的第一個版本一直延續到Python2.7,Python 的默認編碼都是 ASCII。
>>> import sys>>> sys.getdefaultencoding()'ascii'因此在 Python 源代碼,要可以正常保存中文字符就必須先指定utf
8 或者 gbk 格式
# coding=utf-8或者是: #!/usr/bin/python# -*- coding: utf-8 -*-str 與 unicode
在前面咱們介紹過字符,這裏還有必要重複一下字符和字節的區別,字符就是一個符號,好比一個漢字、一個字母、一個數字、一個標點均可以稱爲一個字符,而字節就是字符就是編碼以後轉換而成的二進制序列,一個字節是8個比特位。例如字符 "p" 存儲到硬盤是一串二進制數據 01110000,佔用一個字節。字節方便存儲和網絡傳輸,而字符用於顯示方便閱讀。
在Python2中,字符與字節的表示很微妙,二者的界限很模糊,Python2 中把字符串分爲 unicode 和 str 兩種類型。本質上 str 類型是二進制字節序列, unicode 類型的字符串是字符,下面的示例代碼能夠看出 str 類型的 "禪" 打印出來是十六進制的 \xec\xf8 ,對應的二進制字節序列就是 '11101100 11111000'。
>>> s = '禪'>>> s'\xec\xf8'>>> type(s)<type 'str'>而 unicode 類型的 u"禪" 對應的 unicode 符號是 u'\u7985':
>>> u = u"禪">>> uu'\u7985'>>> type(u)<type 'unicode'>咱們要把 unicode 字符保存到文件或者傳輸到網絡就須要通過編碼處理轉換成二進制形式的 str 類型,因而 python 的字符串提供了 encode 方法,從 unicode 轉換到 str,反之亦然。
encode:
>>> u = u"禪">>> uu'\u7985'>>> u.encode("utf-8")'\xe7\xa6\x85'decode:
>>> s = "禪">>> s.decode("utf-8")u'\u7985'>>>很多初學者怎麼也記不住 str 與 unicode 之間的轉換用 encode 仍是 decode,若是你記住了 str 本質上實際上是一串二進制數據,而 unicode 是字符(符號),編碼(encode)就是把字符(符號)轉換爲 二進制數據的過程,所以 unicode 到 str 的轉換要用 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 之間的轉換關係以後,咱們來看看何時會出現 UnicodeEncodeError、UnicodeDecodeError 錯誤。
UnicodeEncodeError
UnicodeEncodeError 發生在 unicode 字符串轉換成 str 字節序列的時候,來看一個例子,把一串 unicode 字符串保存到文件。
# -*- coding:utf-8 -*-def main(): name = u'Python之禪' f = open("output.txt", "w") f.write(name)錯誤日誌:
UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-7: ordinal not in range(128)
爲何會出現 UnicodeEncodeError?
由於調用 write 方法時,程序會把字符通過編碼轉換成二進制字節序列,內部會有 unicode 到 str 的編碼轉換過程,程序會先判斷字符串是什麼類型,若是是 str,就直接寫入文件,不須要編碼,由於 str 類型的字符串自己就是一串二進制的字節序列了。若是字符串是 unicode 類型,那麼它會先調用 encode 方法把 unicode 字符串轉換成二進制形式的 str 類型,才保存到文件,而 Python2中,encode 方法默認使用 ascii 進行 encde。
至關於:
>>> u"Python之禪".encode("ascii")可是,咱們知道 ASCII 字符集中只包含了128個拉丁字母,不包括中文字符,所以 出現了 'ascii' codec can't encode characters 的錯誤。要正確地使用 encode ,就必須指定一個包含了中文字符的字符集,好比:UTF-八、GBK。
>>> u"Python之禪".encode("utf-8")'Python\xe4\xb9\x8b\xe7\xa6\x85'>>> u"Python之禪".encode("gbk")'Python\xd6\xae\xec\xf8'因此要把 unicode 字符串正確地寫入文件,就應該預先把字符串進行 UTF-8 或 GBK 編碼轉換。
def main(): name = u'Python之禪' name = name.encode('utf-8') with open("output.txt", "w") as f: f.write(name)或者直接寫str類型的字符串:
def main(): name = 'Python之禪' with open("output.txt", "w") as f: f.write(name)固然,把 unicode 字符串正確地寫入文件不止一種方式,但原理是同樣的,這裏再也不介紹,把字符串寫入數據庫,傳輸到網絡都是一樣的原理。
UnicodeDecodeError
UnicodeDecodeError 發生在 str 類型的字節序列解碼成 unicode 類型的字符串時:
>>> a = u"禪">>> au'\u7985'>>> b = a.encode("utf-8")>>> b'\xe7\xa6\x85'>>> b.decode("gbk")Traceback (most recent call last): File "<stdin>", line 1, in <module>UnicodeDecodeError: 'gbk' codec can't decode byte 0x85 in position 2: incomplete multibyte sequence把一個通過 UTF-8 編碼後生成的字節序列 '\xe7\xa6\x85' 再用 GBK 解碼轉換成 unicode 字符串時,出現 UnicodeDecodeError,由於 (對於中文字符)GBK 編碼只佔用兩個字節,而 UTF-8 佔用3個字節,用 GBK 轉換時,還多出一個字節,所以它無法解析。避免 UnicodeDecodeError 的關鍵是保持 編碼和解碼時用的編碼類型一致。
這也回答了文章開頭說的字符 "禪",保存到文件中有可能佔3個字節,有可能佔2個字節,具體處決於 encode 的時候指定的編碼格式是什麼。
再舉一個 UnicodeDecodeError 的例子:
>>> x = u"Python">>> y = "之禪">>> x + yTraceback (most recent call last): File "<stdin>", line 1, in <module>UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)>>>str 與 unicode 字符串 執行 + 操做時,Python 會把 str 類型的字節序列隱式地轉換成(解碼)成 和 x 同樣的 unicode 類型,但Python是使用默認的 ascii 編碼來轉換的,而 ASCII字符集中不包含有中文,因此報錯了。至關於:
>>> y.decode('ascii')Traceback (most recent call last): File "<stdin>", line 1, in <module>UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)正確地方式應該是找到一種包含有中文字符的字符編碼,好比 UTF-8或者 GBK 顯示地把 y 進行解碼轉換成 unicode 類型:
>>> x = u"Python">>> y = "之禪">>> y = y.decode("utf-8")>>> x + yu'Python\u4e4b\u7985'Python3 中的字符串與字節序列
Python3 對字符串和字符編碼進行了很完全的重構,徹底不兼容 Python2,同時也不少想遷移到 Python3 的項目帶來了很大的麻煩,Python3 把系統默認編碼設置爲 UTF-8,字符和二進制字節序列區分得更清晰,分別用 str 和 bytes 表示。文本字符所有用 str 類型表示,str 能表示 Unicode 字符集中全部字符,而二進制字節數據用一種全新的數據類型,用 bytes 來表示,儘管 Python2 中也有 bytes 類型,但那隻不過是 str 的一個別名。
str
>>> a = "a">>> a'a'>>> type(a)<class 'str'>>>> b = "禪">>> b'禪'>>> type(b)<class 'str'>bytes
Python3 中,在字符引號前加‘b’,明確表示這是一個 bytes 類型的對象,實際上它就是一組二進制字節序列組成的數據,bytes 類型能夠是 ASCII 範圍內的字符和其它十六進制形式的字符數據,但不能用中文等非 ASCII 字符表示。
>>> c = b'a'>>> cb'a'>>> type(c)<class 'bytes'>>>> d = b'\xe7\xa6\x85'>>> db'\xe7\xa6\x85'>>> type(d)<class 'bytes'>>>>>>> e = b'禪' File "<stdin>", line 1SyntaxError: bytes can only contain ASCII literal characters.bytes 類型提供的操做和 str 同樣,支持分片、索引、基本數值運算等操做。可是 str 與 bytes 類型的數據不能執行
+
操做,儘管在python2中是可行的。>>> b"a"+b"c" b'ac' >>> b"a"*2 b'aa' >>> b"abcdef\xd6"[1:] b'bcdef\xd6' >>> b"abcdef\xd6"[-1] 214 >>> b"a" + "b" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't concat bytes to strpython2 與 python3 字節與字符對比:
python2 python3 表現 轉換 做用 str bytes 字節 encode 存儲 unicode str 字符 decode 顯示 總結
字符編碼本質上是字符到字節的轉換過程
字符集的演進過程是:ascii、eascii、ios8895-x,gb2312... Unicode
Unicode 是字符集,對應的編碼格式有UTF-8,UTF-16
字節序列存儲的時候有大小端之分
python2 中字符與字節分別用 unicode 和 str 類型表示
python3 中字符與字節分別用 str 與 bytes 表示
參考連接
https://zh.wikipedia.org/wiki/%E4%BD%8D%E5%85%83%E7%B5%84%E9%A0%86%E5%BA%8F%E8%A8%98%E8%99%9F
https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%AD%97%E7%AC%A6%E9%9B%86
http://stackoverflow.com/questions/700187/unicode-utf-ascii-ansi-format-differences
https://www.meridiandiscovery.com/articles/unicode-and-character-encodings/
https://www.praim.com/character-encodings-linux-ascii-utf-8-iso-8859
http://stackoverflow.com/questions/4655250/difference-between-utf-8-and-utf-16