一:什么是編碼
將明文轉換為計算機可以識別的編碼文本稱為“編碼”。反之從計算機可識別的編碼文本轉回為明文為“解碼”。
那么什么是明文呢,首先我們從一段信息說起,消息以人們可以理解,易懂的表示存在,我們把這個表示為明文(plain text)。對於說英文的人,紙張上打印的或者屏幕上顯示的英文都算是明文。
二:都有什么編碼格式?
1:ASCII(占一個字符,只支持英文)
計算機上的數據都是以二進制的形式存儲的,1個字節(8比特)可以表示256種狀態,英文只有26個字符,再加上一些特殊的字符,使用128個就夠了,計算機就可以使用127個不同字節來表示英文文字,這就是ASCII碼
2:GB2312(占兩個字符,支持6700+漢字)
計算機進入中國后,無法顯示中文,一個字節已經被占滿了,我國重新制定了一個編碼表,將擴展的第八位對應的拉丁文全部刪掉,規定一個小於127的字符與原來的意義相同,當兩個大於127的字符連接在一起的時候,就表示一個漢字,前面一個字節為高字節,后面一個字節為低字節,這樣就可以表示7000多漢字,這種編碼叫做GB2312。GB2312是對ASCII的中文擴展
3:GBK和GB18030(GB2312的升級版,支持21000+漢字)
由於漢字的數量太大,GB2312是不能滿足需求,后面規定只要第一個字節大於127就固定表示一個漢字,不管后面的是不是擴展字符里面的內容,擴展后的編碼稱為GBK,GBK包括了GB2312的所有內容,同時增加了近20000個新的漢字和符號
4:Shift-JIS 日本編碼(這里不過多解釋)
5:TIS-620泰國編碼(這里不過多解釋)
6:ks_c_5601-1987韓國編碼(這里不過多解釋)
由於每個國家都有自己的字符,所以其對應關系也涵蓋了自己國家的字符,但是以上編碼都存在局限性,即:僅涵蓋本國字符,無其他國家字符的對應關系。應運而生出現了萬國碼,他涵蓋了全球所有的文字和二進制的對應關系
7:Unicode(2-4字節,已經收錄了136690個字符,並一直擴展)
在uincode出現之前,每隔國家都搞自己的編碼,彼此之間互不支持,帶來了許多不方便,國際標准組織提出來一個統一的編碼標准:unicode
unicode用兩個字符來表示一個字符,可以提供65535種字符,足夠覆蓋世界上所有的字符
8:UTF-8(Unicode Transformation Format)
unicode的出現,提供了統一的標准,但對於英文世界來說,一個字節完全夠用,如果使用unicode會浪費大量的空間,為了解決這個問題提出來utf-8,一種針對unicode的可變長度字符串,可以使用1-4個字符表示一個符號,根據不同的符號變化字節長度,當字符在ASCII編碼范圍內,用一個字節表示,兼用ASCII。
使用這樣編碼的好處是,雖然內存匯總的數據都是unicode,但是當數據保存到磁盤或者用於網絡傳輸時,使用utf-8會節省更多的流量和硬盤空間。
- UTF-8: 使用1、2、3、4個字節表示所有字符;優先使用1個字符、無法滿足則使增加一個字節,最多4個字節。英文占1個字節、歐洲語系占2個、東亞占3個,其它及特殊字符占4個
- UTF-16: 使用2、4個字節表示所有字符;優先使用2個字節,否則使用4個字節表示。
- UTF-32: 使用4個字節表示所有字符;
unicode和utf-8的關系:unicode是內存編碼表示方案,而utf-8是如何保存和傳輸unicode的方案
三:編碼的轉換
雖然國際語言是英語 ,但大家在自己的國家依然說自已的語言,不過出了國, 你就得會英語
編碼也一樣,雖然有了unicode and utf-8 , 但是由於歷史問題,各個國家依然在大量使用自己的編碼,比如中國的windows,默認編碼依然是gbk,而不是utf-8
基於此,如果中國的軟件出口到美國,在美國人的電腦上就會顯示亂碼,因為他們沒有gbk編碼。
若想讓中國的軟件可以正常的在 美國人的電腦上顯示,只有以下2條路可走:
- 讓美國人的電腦上都裝上gbk編碼
- 把你的軟件編碼以utf-8編碼
第1種方法幾乎不可能實現,第2種方法比較簡單。 但是也只能是針對新開發的軟件。 如果你之前開發的軟件就是以gbk編碼的,上百萬行代碼可能已經寫出去了,重新編碼成utf-8格式也會費很大力氣。
so , 針對已經用gbk開發完畢的項目,以上2種方案都不能輕松的讓項目在美國人電腦上正常顯示,難道沒有別的辦法了么?
有, 還記得我們、、講unicode其中一個功能是其包含了跟全球所有國家編碼的映射關系,意思就是,你寫的是gbk的“學習”,但是unicode能自動知道它在unicode中的“學習”的編碼是什么,如果這樣的話,那是不是意味着,無論你以什么編碼存儲的數據 ,只要你的軟件在把數據從硬盤讀到內存里,轉成unicode來顯示,就可以了。
由於所有的系統、編程語言都默認支持unicode,那你的gbk軟件放到美國電腦 上,加載到內存里,變成了unicode,中文就可以正常展示啦。
unicode與gbk的映射表 http://www.unicode.org/charts/
四:python2.x的編碼
在python2.x中,有兩種字符串類型:str類型和unicode類型。這兩個類型只是python定義的兩個名字,關鍵還要看這兩種數據類型在內存中的存儲方法是什么
str和unicode都是basestring的子類。嚴格意義上說,str其實是字節串,它是unicode經過編碼后的字節組成的序列。
而unicode是一個字符串,str是unicode這個字符串經過編碼(utf8,gbk等)后的字節組成的序列。
unicode才是真正意義上的字符串,對字節串str使用正確的字符編碼進行解碼后獲得
在Py2里,str=bytes。python2的字符串其實更應該稱為字節串
py2編碼的最大特點是Python 2 將會自動的將bytes數據解碼成 unicode 字符串
所以在2里我們可以將字節與字符串拼接。
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> s = '學習' >>> print type(s) <type 'str'> >>> print repr(s) '\xd1\xa7\xcf\xb0'
>>> s = u'學習'
>>> print type(s)
<type 'unicode'>
>>> print repr(s)
u'\u5b66\u4e60'
由上面的例子可以看出str和unicode分別存儲的是字節數據和unicode數據
但是,python2.x悄悄掩蓋掉了byte到unicode的轉換,只要數據全部是ASCII的話,所有的轉換都是正確的,一個一個非ASCII的字符進入你的程序,那么默認的解碼將會失效,從而造成unicodedecodeerror的錯誤,py2編碼讓程序在出路ASCII的時候非常簡單,付出的代價就是在處理非ASCII的時候將會失敗。
由於Python創始人在開發初期認知的局限性,其並未預料到python能發展成一個全球流行的語言,導致其開發初期並沒有把支持全球各國語言當做重要的事情來做,所以就輕佻的把ASCII當做了默認編碼。 當后來大家對支持漢字、日文、法語等語言的呼聲越來越高時,Python於是准備引入unicode,但若直接把默認編碼改成unicode的話是不現實的, 因為很多軟件就是基於之前的默認編碼ASCII開發的,編碼一換,那些軟件的編碼就都亂了。所以Python 2 就直接 搞了一個新的字符類型,就叫unicode類型,比如你想讓你的中文在全球所有電腦上正常顯示,在內存里就得把字符串存成unicode類型
五:python3.x的編碼
python3.x也有兩種數據類型,str和bytes;str類型存unicode數據,bytes類型存bytes數據,
python3.x將utf-8或者gbk等編碼的字節數據轉化為python3.x的str類型,utf-8編碼的bytes<-->str
python2.x將utf-8或者gbk等編碼的字節數據轉化為python2.x的unicode類型,utf-8編碼的str<-->unicode
python3.x的編碼思想就是它清晰的將文本和二進制區分開了,不會對bytes字節進行自動編碼。文本總是unicode,由str類型表示,二進制數據則由bytes類型表示,Python 3不會以任意隱式的方式混用str和bytes,將兩者明確地區分開。基於此,Python3中不能拼接字符串和字節包,也不可以在字節包里搜索字符串(反之亦然),也不能向使用字符串參數的函數中傳入字節包參數(反之亦然)。
python3 執行代碼的過程
- 解釋器找到代碼文件,把代碼字符串按文件頭定義的編碼加載到內存,轉成unicode
- 把代碼字符串按照語法規則進行解釋,
- 所有的變量字符都會以unicode編碼聲明
######在python2.x####### print(b'hello'+'world') 會輸出 helloworld ######在python3.x####### print(b'hello'+'world') 會輸出 TypeError: can't concat str to bytes
本來這樣就可以結束了,但是上面的utf-8編碼之所以能在windows gbk的終端下顯示正常,是因為到了內存里python解釋器把utf-8轉成了unicode , 但是這只是python3, 並不是所有的編程語言在內存里默認編碼都是unicode,比如 萬惡的python2 就不是, 它的默認編碼是ASCII,想寫中文,就必須聲明文件頭的coding為gbk or utf-8, 聲明之后,python2解釋器僅以文件頭聲明的編碼去解釋你的代碼,加載到內存后,並不會主動幫你轉為unicode,也就是說,你的文件編碼是utf-8,加載到內存里,你的變量字符串就也是utf-8, 這意味着什么你知道么?。。。意味着,你以utf-8編碼的文件,在windows是亂碼。
亂是正常的,不亂才不正常,因為只有2種情況 ,你的windows上顯示才不會亂
- 字符串以GBK格式顯示
- 字符串是unicode編碼
既然Python2並不會自動的把文件編碼轉為unicode存在內存里, 那就只能使出最后一招了,你自己人肉轉。Py3 自動把文件編碼轉為unicode必定是調用了什么方法,這個方法就是,decode(解碼) 和encode(編碼)
時間來到2008年,python發展已近20年,創始人龜叔越來越覺得python里的好多東西已發展的不像他的初衷那樣,開始變得臃腫、不簡潔、且有些設計讓人摸不到頭腦,比如unicode 與str類型,str 與bytes類型的關系,這給很多python程序員造成了困擾。
龜叔再也忍不了,像之前一樣的修修補補已不能讓Python變的更好,於是來了個大變革,Python3橫空出世,不兼容python2,python3比python2做了非常多的改進,其中一個就是終於把字符串變成了unicode,文件默認編碼變成了utf-8,這意味着,只要用python3,無論你的程序是以哪種編碼開發的,都可以在全球各國電腦上正常顯示,真是太棒啦!
PY3 除了把字符串的編碼改成了unicode, 還把str 和bytes 做了明確區分, str 就是unicode格式的字符, bytes就是單純二進制啦。
六:文件存儲讀取過程中的編碼問題
對於文本編輯器word等軟件,當我們在這些軟件上編輯文字的時候,無論是什么語言的文字或符號,計算機都是無法識別的。
那么在保存之前數據是通過什么形式存在內存的呢?
是unicode數據,為什么要存unicode數據,這是因為無論世界上的任何字符它都有唯一編碼對應,兼容性是最好的。
當我們保存了存到磁盤上的數據又是什么呢?
是通過某種編碼方式編碼的bytes字節串。比如utf8---一種可變長編碼,很好的節省了空間;還可以是gbk等編碼方式。
在我們的文本編輯器軟件都有默認的保存文件的編碼方式,比如utf-8,gbk等。當我們保存的時候,這些編輯軟件已經"默默地"做了編碼工作。
那當我們再打開這個文件時,軟件又默默地給我們做了解碼的工作,將數據再解碼成unicode,然后就可以呈現明文給用戶了!
所以,unicode是離用戶更近的數據,bytes是離計算機更近的數據。
七:編碼與程序運行的關系
編寫python代碼一般會用到pycharm ,sublime等軟件,而代碼文件的創建,保存,執行等過程就伴隨着編碼解碼流程,使用pycharm創建的hello.py文件,當我們保存的時候,文件就以pycharm默認的編碼方式保存到磁盤,關閉文件再打開,pycharm就會以默認的編碼方式對該文件打開后讀到的內容就行解碼,轉成unicode到內存我們就看到了我們的明文;
而如果點開運行按鈕或者在命令行運行該文件時候,python解釋器就會被調用,打開文件,然后將存儲在磁盤上的bytes數據解碼成unicode數據,這個過程和編譯器是一樣的,不同的是解釋器將會把這些unicode數據翻譯成c代碼再轉成二進制的數據流,最后通過控制操作系統調用cpu來執行這些二進制數據,整個過程才算結束,
python2.x默認的是ASCII碼,python3.x默認的是utf-8,可以通過下面的方式查詢:
import sys
print(sys.getdefaultencoding())
八,字符編碼轉換總結
python2.x
內存中字符默認編碼是ASCII,默認文件編碼也是ASCII
當聲明了文件頭的編碼后,字符串的編碼就按照文件編碼來,總之,文件編碼是什么,那么python2.x的str就是什么
python2.x的unicode是一個單獨的類型,按u"編碼"來表示
python2.x str==bytes,bytes直接是按照字符編碼存成2進制格式在內存里
python3.x
字符串都是unicode
文件編碼都默認是utf-8,讀到內存會被python解釋器自動轉成unicode
bytes和str做了明確的區分
所有的unicode字符編碼后都會編程bytes格式
九:print語句和print函數的區別
print語句
在python2.x中,print語句最簡單的使用形式是
print hello world!
這相當於執行了
sys.stdout.write(‘’。join(map(str,[hello world!]))+'\n')
如果以逗號為分隔符,傳遞額外的參數,這些參數會被傳遞到str()函數,最終打印的時候,每個參數之間會空一行。
從2.0版本開始,python引入了print>>的語法。作用是重定向print語句最終輸出的字符串的文件
例如:
print>>output 相當於 output.write(str(hello)+'\n')
print函數
如果用python來實現print的函數,他的函數定義應該是這樣的
import sys def print(*objects, sep=None, end=None, file=None, flush=False): """A Python translation of the C code for builtins.print(). """ if sep is None: sep = ' ' if end is None: end = '\n' if file is None: file = sys.stdout file.write(sep.join(map(str, objects)) + end) if flush: file.flush() 函數定義
從上面的代碼我們可以發現,python3.x的print 函數實現了print語句的所有特性。