一. 什么是編碼
將明文轉換為計算機可以識別的的編碼文本稱為“編碼”, 反之從計算機可識別的編碼文本轉回明文為“解碼”
1. ASCII
計算機上的數(shù)據(jù)都是以二進制的形式存儲的,1個字節(jié)(8比特)可以表示256種狀態(tài),英文只有26個字符,再加上一些特殊字符,使用128個就夠了,計算機就可以使用127個不同字節(jié)來存儲英語文字,這就是ASCII編碼
最開始的時候8位中的最高位是沒有用到的,后來為了表示拉丁文,將最高位用上形成擴展ASCII編碼,一個字節(jié)就用滿了。
2. GB2312
計算機進入中國后,無法顯示中文,一個自己已經(jīng)被占滿了,我國重新制定了一個編碼表,將擴展的第八位對應的拉丁文全部刪掉,規(guī)定一個小于127的字符與原來的意義相同,當兩個大于127的字符鏈接在一起的時候,就表示一個漢字,前面一個字節(jié)為高字節(jié)(0xA1-0xF7),后面一個字節(jié)為低字節(jié)(0xA1-0xFE),這樣可以表示7000多個漢字,這種編碼叫做GB2312。GB2312是對ASCII的中文擴展
3. GBK和GB18030
由于漢字的數(shù)量太大,GB2312不能滿足需求,后來規(guī)定只要第一個字節(jié)大于127就固定表示一個漢字,不管后面的是不是擴展字符集里面的內(nèi)容,擴展后的編碼成為GBK, GBK包括了GB2312的所有內(nèi)容,同時增加了近20000個新的漢字(包括繁體)和符號
4. Unicode
在Unicode出現(xiàn)之前,每個國家都搞自己的編碼,彼此之間互不支持,帶來很多麻煩,國際標標準組織提出來一個統(tǒng)一的編碼標準:Unicode
Unicode用兩個字符來表示一個字符,可以提供65535種字符,足夠覆蓋世界上的所有符號
5. utf-8
Unicode的出現(xiàn),提供了統(tǒng)一的標準,但對于英文世界的國家來說,一個字節(jié)完全夠用,如果使用Unicode會浪費大量空間,為了解決這個問題提出了utf-8,一種針對Unicode的可變長度字符編碼,可以使用1-4個字節(jié)表示一個符號,根據(jù)不同的符號變化字節(jié)長度,當字符在ASCII編碼范圍時,用一個字節(jié)表示,兼用ASCII。
使用這樣的編碼的好處是,雖然內(nèi)存匯總的數(shù)據(jù)都是Unicode,但當數(shù)據(jù)保存到磁盤或者用于網(wǎng)絡傳輸時,使用utf-8會節(jié)省更多的流量和硬盤空間。
Unicode和utf-8的關系:Unicode是內(nèi)存編碼表示方案(規(guī)范),而utf-8是如何保存和傳輸Unicode的方案(實現(xiàn))
二. Python2 中的sting編碼
在Python2中,有兩種字符串類型:str類型和Unicode類型。這兩個類型只是Python定義的兩個名字,關鍵還要看這兩種數(shù)據(jù)類型在內(nèi)存中的存儲方式是什么。
>>> s1 = '中'
>>> print type(s1)
<type 'str'>
>>> print repr(s1)
'\xd6\xd0'
>>> s2 = u'中'
>>> print type(s2)
<type 'unicode'>
>>> print repr(s2)
u'\u4e2d'
由此可以看出str和Unicode分別存儲的是字節(jié)數(shù)據(jù)和Unicode數(shù)據(jù).那么這兩種數(shù)據(jù)之間的關系是什么樣的?
>>> s1 = u'中'
>>> print repr(s1)
u'\u4e2d'
>>>
>>> b = s1.encode('utf-8')
>>> print b
中
>>> print type(b)
<type 'str'>
>>> print repr(b)
'\xe4\xb8\xad'
>>>
>>> s2 = '中國'
>>> u = s2.decode('utf-8')
>>> print u
中國
>>> print type(u)
<type 'unicode'>
>>> print repr(u)
u'\u4e2d\u56fd'
s2的“中國”在Windows系統(tǒng)中需要使用gbk來解碼, 無論utf-8還是gbk都只是一種編碼編碼規(guī)則,一種把Unicode數(shù)據(jù)編碼成字節(jié)數(shù)據(jù)的規(guī)則。所以utf-8編碼的字節(jié)一定要utf-8解碼,否則亂碼或者報錯。
# -*-coding:utf-8-*-
print '中國' # 中國
print repr('中國') # '\xe4\xb8\xad\xe5\x9b\xbd'
print (u'hello' + 'world') # helloworld
print (u'中國' + '人民') #UnicodeDecodeError:
#'ascii' codec can't decode byte 0xe4
#in position 0: ordinal not in range(128)
Python 2 悄悄掩蓋掉了 byte 到 unicode 的轉換,只要數(shù)據(jù)全部是 ASCII 的話,所有的轉換都是正確的,一旦一個非 ASCII 字符偷偷進入你的程序,那么默認的解碼將會失效,從而造成 UnicodeDecodeError 的錯誤。py2編碼讓程序在處理 ASCII 的時候更加簡單。付出的代價就是在處理非 ASCII 的時候將會失敗。
三. Python3中的string編碼
python3 renamed the unicode type to str ,the old str type has been replaced by bytes.
** py3也有兩種數(shù)據(jù)類型:str和bytes; str類型存unicode數(shù)據(jù),bytse類型存bytes數(shù)據(jù),與py2比只是換了一下名字而已。**
python3中將utf-8或者gbk等編碼的字節(jié)數(shù)據(jù)轉為Python3中的str類型, utf-8編碼的bytes <---> str
python2中將 utf-8或者gbk等編碼的str編解碼為Python2中的Unicode, utf-8編碼的str <---> unicode
Python3中的編碼思想
Python 3清晰地將文本和二進制數(shù)據(jù)區(qū)分開了,不會對bytes字節(jié)串進行自動解碼。文本總是Unicode,由str類型表示,二進制數(shù)據(jù)則由bytes類型表示。Python 3不會以任意隱式的方式混用str和bytes,將兩者明確地區(qū)分開?;诖?,Python3中不能拼接字符串和字節(jié)包,也不可以在字節(jié)包里搜索字符串(反之亦然),也不能向使用字符串參數(shù)的函數(shù)中傳入字節(jié)包參數(shù)(反之亦然)。
四. 文件存儲讀取過程中的編碼問題
對于文本編輯器word等軟件,當我們在這些軟件上編輯文字的時候,無論是什么語言的文字或符號,計算機都是無法識別的。
那么在保存之前數(shù)據(jù)是通過什么形式存在內(nèi)存的呢?
是unicode數(shù)據(jù),為什么要存unicode數(shù)據(jù),這是因為無論世界上的任何字符它都有唯一編碼對應,兼容性是最好的。
當我們保存了存到磁盤上的數(shù)據(jù)又是什么呢?
是通過某種編碼方式編碼的bytes字節(jié)串。比如utf8---一種可變長編碼,很好的節(jié)省了空間;還可以是gbk等編碼方式。
在我們的文本編輯器軟件都有默認的保存文件的編碼方式,比如utf-8,gbk等。當我們保存的時候,這些編輯軟件已經(jīng)"默默地"做了編碼工作。
那當我們再打開這個文件時,軟件又默默地給我們做了解碼的工作,將數(shù)據(jù)再解碼成unicode,然后就可以呈現(xiàn)明文給用戶了!
所以,unicode是離用戶更近的數(shù)據(jù),bytes是離計算機更近的數(shù)據(jù)。
編碼與程序運行的關系
編寫Python代碼一般會用到sublime,pycharm,vim等軟件。而代碼文件的創(chuàng)建、保存、執(zhí)行等過程就伴隨著編解碼流程。下面以pycharm為例介紹這一過程。
使用pycharm創(chuàng)建hello.py文件,當我們保存的的時候,hello.py文件就以pycharm默認的編碼方式保存到了磁盤;關閉文件后再打開,pycharm就再以默認的編碼方式對該文件打開后讀到的內(nèi)容進行解碼,轉成unicode到內(nèi)存我們就看到了我們的明文;
而如果我們點擊運行按鈕或者在命令行運行該文件時,Python解釋器這個軟件就會被調(diào)用,打開文件,然后將存儲在磁盤上的bytes數(shù)據(jù)解碼成unicode數(shù)據(jù),這個過程和編輯器是一樣的,不同的是解釋器會再將這些unicode數(shù)據(jù)翻譯成C代碼再轉成二進制的數(shù)據(jù)流,最后通過控制操作系統(tǒng)調(diào)用cpu來執(zhí)行這些二進制數(shù)據(jù),整個過程才算結束。
py2默認ASCII碼,py3默認的utf8,可以通過如下方式查詢
import sys
print(sys.getdefaultencoding())
使用Python2需要在編碼文件頭加一行#-*-coding:utf-8-*-,是因為如果py2解釋器去執(zhí)行一個utf8編碼的文件,就會以默認地ASCII去解碼utf8,一旦程序中有中文,自然就解碼錯誤了,所以我們在文件開頭位置聲明一下告訴解釋器不要以默認的編碼方式去解碼這個文件,而是以utf8來解碼。
而Python3的解釋器因為默認utf8編碼,不存在這樣的問題。