字符編碼


一 了解字符編碼的知識儲備

一 計算機基礎知識

 

二 文本編輯器存取文件的原理(nodepad++,pycharm,word)

#1、打開編輯器就打開了啟動了一個進程,是在內存中的,所以,用編輯器編寫的內容也都是存放與內存中的,斷電后數據丟失

#2、要想永久保存,需要點擊保存按鈕:編輯器把內存的數據刷到了硬盤上。

#3、在我們編寫一個py文件(沒有執行),跟編寫其他文件沒有任何區別,都只是在編寫一堆字符而已。

三 python解釋器執行py文件的原理 ,例如python test.py

復制代碼
#第一階段:python解釋器啟動,此時就相當於啟動了一個文本編輯器

#第二階段:python解釋器相當於文本編輯器,去打開test.py文件,從硬盤上將test.py的文件內容讀入到內存中(小復習:pyhon的解釋性,決定了解釋器只關心文件內容,不關心文件后綴名)

#第三階段:python解釋器解釋執行剛剛加載到內存中test.py的代碼( ps:在該階段,即真正執行代碼時,才會識別python的語法,執行文件內代碼,當執行到name="egon"時,會開辟內存空間存放字符串"egon")
復制代碼

四 總結python解釋器與文件本編輯的異同

#1、相同點:python解釋器是解釋執行文件內容的,因而python解釋器具備讀py文件的功能,這一點與文本編輯器一樣

#2、不同點:文本編輯器將文件內容讀入內存后,是為了顯示或者編輯,根本不去理會python的語法,而python解釋器將文件內容讀入內存后,可不是為了給你瞅一眼python代碼寫的啥,而是為了執行python代碼、會識別python語法。

二 字符編碼介紹

一 什么是字符編碼

復制代碼
  計算機要想工作必須通電,即用‘電’驅使計算機干活,也就是說‘電’的特性決定了計算機的特性。電的特性即高低電平(人類從邏輯上將二進制數1對應高電平,二進制數0對應低電平),關於磁盤的磁特性也是同樣的道理。結論:計算機只認識數字

  很明顯,我們平時在使用計算機時,用的都是人類能讀懂的字符(用高級語言編程的結果也無非是在文件內寫了一堆字符),如何能讓計算機讀懂人類的字符?

  必須經過一個過程:
  #字符--------(翻譯過程)------->數字 

  #這個過程實際就是一個字符如何對應一個特定數字的標准,這個標准稱之為字符編碼
復制代碼

二 以下兩個場景下涉及到字符編碼的問題:

#1、一個python文件中的內容是由一堆字符組成的,存取均涉及到字符編碼問題(python文件並未執行,前兩個階段均屬於該范疇)

#2、python中的數據類型字符串是由一串字符組成的(python文件執行時,即第三個階段)

三 字符編碼的發展史與分類(了解)

計算機由美國人發明,最早的字符編碼為ASCII,只規定了英文字母數字和一些特殊字符與數字的對應關系。最多只能用 8 位來表示(一個字節),即:2**8 = 256,所以,ASCII碼最多只能表示 256 個符號

當然我們編程語言都用英文沒問題,ASCII夠用,但是在處理數據時,不同的國家有不同的語言,日本人會在自己的程序中加入日文,中國人會加入中文。

而要表示中文,單拿一個字節表表示一個漢子,是不可能表達完的(連小學生都認識兩千多個漢字),解決方法只有一個,就是一個字節用>8位2進制代表,位數越多,代表的變化就多,這樣,就可以盡可能多的表達出不通的漢字

所以中國人規定了自己的標准gb2312編碼,規定了包含中文在內的字符->數字的對應關系。

日本人規定了自己的Shift_JIS編碼

韓國人規定了自己的Euc-kr編碼(另外,韓國人說,計算機是他們發明的,要求世界統一用韓國編碼,但世界人民沒有搭理他們)

 

這時候問題出現了,精通18國語言的小周同學謙虛的用8國語言寫了一篇文檔,那么這篇文檔,按照哪國的標准,都會出現亂碼(因為此刻的各種標准都只是規定了自己國家的文字在內的字符跟數字的對應關系,如果單純采用一種國家的編碼格式,那么其余國家語言的文字在解析時就會出現亂碼)

所以迫切需要一個世界的標准(能包含全世界的語言)於是unicode應運而生(韓國人表示不服,然后沒有什么卵用)

ascii用1個字節(8位二進制)代表一個字符

unicode常用2個字節(16位二進制)代表一個字符,生僻字需要用4個字節

例:

字母x,用ascii表示是十進制的120,二進制0111 1000

漢字已經超出了ASCII編碼的范圍,用Unicode編碼是十進制的20013,二進制的01001110 00101101

字母x,用unicode表示二進制0000 0000 0111 1000,所以unicode兼容ascii,也兼容萬國,是世界的標准

 

這時候亂碼問題消失了,所有的文檔我們都使用但是新問題出現了,如果我們的文檔通篇都是英文,你用unicode會比ascii耗費多一倍的空間,在存儲和傳輸上十分的低效

本着節約的精神,又出現了把Unicode編碼轉化為“可變長編碼”的UTF-8編碼。UTF-8編碼把一個Unicode字符根據不同的數字大小編碼成1-6個字節,常用的英文字母被編碼成1個字節,漢字通常是3個字節,只有很生僻的字符才會被編碼成4-6個字節。如果你要傳輸的文本包含大量英文字符,用UTF-8編碼就能節省空間:

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

從上面的表格還可以發現,UTF-8編碼有一個額外的好處,就是ASCII編碼實際上可以被看成是UTF-8編碼的一部分,所以,大量只支持ASCII編碼的歷史遺留軟件可以在UTF-8編碼下繼續工作。

四 總結字符編碼的發展可分為三個階段(重要)

#階段一:現代計算機起源於美國,最早誕生也是基於英文考慮的ASCII
ASCII:一個Bytes代表一個字符(英文字符/鍵盤上的所有其他字符),1Bytes=8bit,8bit可以表示0-2**8-1種變化,即可以表示256個字符

ASCII最初只用了后七位,127個數字,已經完全能夠代表鍵盤上所有的字符了(英文字符/鍵盤的所有其他字符),后來為了將拉丁文也編碼進了ASCII表,將最高位也占用了

#階段二:為了滿足中文和英文,中國人定制了GBK
GBK:2Bytes代表一個中文字符,1Bytes表示一個英文字符
為了滿足其他國家,各個國家紛紛定制了自己的編碼
日本把日文編到Shift_JIS里,韓國把韓文編到Euc-kr里

#階段三:各國有各國的標准,就會不可避免地出現沖突,結果就是,在多語言混合的文本中,顯示出來會有亂碼。如何解決這個問題呢???

#!!!!!!!!!!!!非常重要!!!!!!!!!!!!
說白了亂碼問題的本質就是不統一,如果我們能統一全世界,規定全世界只能使用一種文字符號,然后統一使用一種編碼,那么亂碼問題將不復存在,
ps:就像當年秦始皇統一中國一樣,書同文車同軌,所有的麻煩事全部解決
很明顯,上述的假設是不可能成立的。很多地方或老的系統、應用軟件仍會采用各種各樣的編碼,這是歷史遺留問題。於是我們必須找出一種解決方案或者說編碼方案,需要同時滿足:
#1、能夠兼容萬國字符
#2、與全世界所有的字符編碼都有映射關系,這樣就可以轉換成任意國家的字符編碼

這就是unicode(定長), 統一用2Bytes代表一個字符, 雖然2**16-1=65535,但unicode卻可以存放100w+個字符,因為unicode存放了與其他編碼的映射關系,准確地說unicode並不是一種嚴格意義上的字符編碼表,下載pdf來查看unicode的詳情:
鏈接:https://pan.baidu.com/s/1dEV3RYp

很明顯對於通篇都是英文的文本來說,unicode的式無疑是多了一倍的存儲空間(二進制最終都是以電或者磁的方式存儲到存儲介質中的)

於是產生了UTF-8(可變長,全稱Unicode Transformation Format),對英文字符只用1Bytes表示,對中文字符用3Bytes,對其他生僻字用更多的Bytes去存


#總結:內存中統一采用unicode,浪費空間來換取可以轉換成任意編碼(不亂碼),硬盤可以采用各種編碼,如utf-8,保證存放於硬盤或者基於網絡傳輸的數據量很小,提高傳輸效率與穩定性。
!!!重點!!!

基於目前的現狀,內存中的編碼固定就是unicode,我們唯一可變的就是硬盤的上對應的字符編碼。
此時你可能會覺得,那如果我們以后開發軟時統一都用unicode編碼,那么不就都統一了嗎,關於統一這一點你的思路是沒錯的,但我們不可會使用unicode編碼來編寫程序的文件,因為在通篇都是英文的情況下,耗費的空間幾乎會多出一倍,這樣在軟件讀入內存或寫入磁盤時,都會徒增IO次數,從而降低程序的執行效率。因而我們以后在編寫程序的文件時應該統一使用一個更為精准的字符編碼utf-8(用1Bytes存英文,3Bytes存中文),再次強調,內存中的編碼固定使用unicode。
1、在存入磁盤時,需要將unicode轉成一種更為精准的格式,utf-8:全稱Unicode Transformation Format,將數據量控制到最精簡

2、在讀入內存時,需要將utf-8轉成unicode
所以我們需要明確:內存中用unicode是為了兼容萬國軟件,即便是硬盤中有各國編碼編寫的軟件,unicode也有相對應的映射關系,但在現在的開發中,程序員普遍使用utf-8編碼了,估計在將來的某一天等所有老的軟件都淘汰掉了情況下,就可以變成:內存utf-8<->硬盤utf-8的形式了。

三 字符編碼應用之文件編輯器

3.1 文本編輯器之nodpad++

 

首先明確概念
#1、文件從內存刷到硬盤的操作簡稱存文件
#2、文件從硬盤讀到內存的操作簡稱讀文件

亂碼的兩種情況:
#亂碼一:存文件時就已經亂碼
存文件時,由於文件內有各個國家的文字,我們單以shiftjis去存,
本質上其他國家的文字由於在shiftjis中沒有找到對應關系而導致存儲失敗
但當我們硬要存的時候,編輯並不會報錯(難道你的編碼錯誤,編輯器這個軟件就跟着崩潰了嗎???),但毫無疑問,不能存而硬存,肯定是亂存了,即存文件階段就已經發生亂碼
而當我們用shiftjis打開文件時,日文可以正常顯示,而中文則亂碼了

#用open模擬編輯器的過程
可以用open函數的write可以測試,f=open('a.txt','w',encodig='shift_jis'
f.write('你瞅啥\n何を見て\n') #'你瞅啥'因為在shiftjis中沒有找到對應關系而無法保存成功,只存'何を見て\n'可以成功

#以任何編碼打開文件a.txt都會出現其余兩個無法正常顯示的問題
f=open('a.txt','wb')
f.write('何を見て\n'.encode('shift_jis'))
f.write('你愁啥\n'.encode('gbk'))
f.write('你愁啥\n'.encode('utf-8'))
f.close()





#亂碼二:存文件時不亂碼而讀文件時亂碼
存文件時用utf-8編碼,保證兼容萬國,不會亂碼,而讀文件時選擇了錯誤的解碼方式,比如gbk,則在讀階段發生亂碼,讀階段發生亂碼是可以解決的,選對正確的解碼方式就ok了,
!!!亂碼分析!!! 

3.2 文本編輯器之pycharm

以utf-8格式打開(選擇reload)

#reload與convert的區別:

pycharm非常強大,提供了自動幫我們convert轉換的功能,即將字符按照正確的格式轉換

要自己探究字符編碼的本質,還是不要用這個

我們選擇reload,即按照某種編碼重新加載文件
pycharm中:reload與convert的區別

3.3 文本編輯器之python解釋器

復制代碼
文件test.py以gbk格式保存,內容為:

  x=''

無論是

  python2 test.py

還是

  python3 test.py

都會報錯(因為python2默認ascii,python3默認utf-8)

除非在文件開頭指定#coding:gbk
復制代碼

3.4 總結

!!!總結非常重要的兩點!!!

#1、保證不亂嗎的核心法則就是,字符按照什么標准而編碼的,就要按照什么標准解碼,此處的標准指的就是字符編碼

#2、在內存中寫的所有字符,一視同仁,都是unicode編碼,比如我們打開編輯器,輸入一個“你”,我們並不能說“你”就是一個漢字,此時它僅僅只是一個符號,該符號可能很多國家都在使用,根據我們使用的輸入法不同這個字的樣式可能也不太一樣。只有在我們往硬盤保存或者基於網絡傳輸時,才能確定”你“到底是一個漢字,還是一個日本字,這就是unicode轉換成其他編碼格式的過程了

                  unicode----->encode-------->utf-8

                  utf-8-------->decode---------->unicode

#補充:

瀏覽網頁的時候,服務器會把動態生成的Unicode內容轉換為UTF-8再傳輸到瀏覽器

如果服務端encode的編碼格式是utf-8, 客戶端內存中收到的也是utf-8編碼的結果。

 

四 字符編碼應用之python

4.1 執行python程序的三個階段

python test.py   (我再強調一遍,執行test.py的第一步,一定是先將文件內容讀入到內存中

test.py文件內容以gbk格式保存的,內容為:

階段一:啟動python解釋器

階段二:python解釋器此時就是一個文本編輯器,負責打開文件test.py,即從硬盤中讀取test.py的內容到內存中

此時,python解釋器會讀取test.py的第一行內容,#coding:utf-8,來決定以什么編碼格式來讀入內存,這一行就是來設定python解釋器這個軟件的編碼使用的編碼格式這個編碼,

可以用sys.getdefaultencoding()查看,如果不在python文件指定頭信息#-*-coding:utf-8-*-,那就使用默認的

python2中默認使用ascii,python3中默認使用utf-8 

 

改正:在test.py指定文件頭,字符編碼一定要為gbk,

#coding:gbk
你好啊

階段三:讀取已經加載到內存的代碼(unicode編碼格式),然后執行,執行過程中可能會開辟新的內存空間,比如x="egon"

復制代碼
內存的編碼使用unicode,不代表內存中全都是unicode,

在程序執行之前,內存中確實都是unicode,比如從文件中讀取了一行x="egon",其中的x,等號,引號,地位都一樣,都是普通字符而已,都是以unicode的格式存放於內存中的

但是程序在執行過程中,會申請內存(與程序代碼所存在的內存是倆個空間)用來存放python的數據類型的值,而python的字符串類型又涉及到了字符的概念

比如x="egon",會被python解釋器識別為字符串,會申請內存空間來存放字符串類型的值,至於該字符串類型的值被識別成何種編碼存放,這就與python解釋器的有關了,而python2與python3的字符串類型又有所不同。 
復制代碼

4.2 python2與python3字符串類型的區別

一 在python2中有兩種字符串類型str和unicode

str類型

當python解釋器執行到產生字符串的代碼時(例如x='上'),會申請新的內存地址,然后將'上'編碼成文件開頭指定的編碼格式

要想看x在內存中的真實格式,可以將其放入列表中再打印,而不要直接打印,因為直接print()會自動轉換編碼,這一點我們稍后再說。

#coding:gbk
x=''
y=''
print([x,y]) #['\xc9\xcf', '\xcf\xc2']
#\x代表16進制,此處是c9cf總共4位16進制數,一個16進制四4個比特位,4個16進制數則是16個比特位,即2個Bytes,這就證明了按照gbk編碼中文用2Bytes
print(type(x),type(y)) #(<type 'str'>, <type 'str'>)

理解字符編碼的關鍵!!!

內存中的數據通常用16進制表示,2位16進制數據代表一個字節,如\xc9,代表兩位16進制,一個字節

gbk存中文需要2個bytes,而存英文則需要1個bytes,它是如何做到的???!!!

gbk會在每個bytes,即8位bit的第一個位作為標志位,標志位為1則表示是中文字符,如果標志位為0則表示為英文字符

x=‘你a好’
轉成gbk格式二進制位
8bit+8bit+8bit+8bit+8bit=(1+7bit)+(1+7bit)+(0+7bit)+(1+7bit)+(1+7bit)

這樣計算機按照從左往右的順序讀:

#連續讀到前兩個括號內的首位標志位均為1,則構成一個中午字符:你

#讀到第三個括號的首位標志為0,則該8bit代表一個英文字符:a

#連續讀到后兩個括號內的首位標志位均為1,則構成一個中午字符:好

也就是說,每個Bytes留給我們用來存真正值的有效位數只有7位,而在unicode表中存放的只是這有效的7位,至於首位的標志位與具體的編碼有關,即在unicode中表示gbk的方式為:

(7bit)+(7bit)+(7bit)+(7bit)+(7bit)

 

按照上圖翻譯的結果,我們可以去unicode關於漢字的對應關系中去查:鏈接:https://pan.baidu.com/s/1dEV3RYp

 

可以看到“”上“”對應的gbk(G0代表的是gbk)編碼就為494F,即我們得出的結果,而上對應的unicode編碼為4E0A,我們可以將gbk-->decode-->unicode

#coding:gbk
x=''.decode('gbk')
y=''.decode('gbk')
print([x,y]) #[u'\u4e0a', u'\u4e0b']

unicode類型

當python解釋器執行到產生字符串的代碼時(例如s=u'林'),會申請新的內存地址,然后將'林'以unicode的格式存放到新的內存空間中,所以s只能encode,不能decode

#coding:gbk
x=u'' #等同於 x='上'.decode('gbk')
y=u'' #等同於 y='下'.decode('gbk')
print([x,y]) #[u'\u4e0a', u'\u4e0b']
print(type(x),type(y)) #(<type 'unicode'>, <type 'unicode'>)

打印到終端

對於print需要特別說明的是:

當程序執行時,比如

x='上' #gbk下,字符串存放為\xc9\xcf

print(x) #這一步是將x指向的那塊新的內存空間(非代碼所在的內存空間)中的內存,打印到終端,按理說應該是存的什么就打印什么,但打印\xc9\xcf,對一些不熟知python編碼的程序員,立馬就懵逼了,所以龜叔自作主張,在print(x)時,使用終端的編碼格式,將內存中的\xc9\xcf轉成字符顯示,此時就需要終端編碼必須為gbk,否則無法正常顯示原內容:上

對於unicode格式的數據來說,無論怎么打印,都不會亂碼

unicode這么好,不會亂碼,那python2為何還那么別扭,搞一個str出來呢?python誕生之時,unicode並未像今天這樣普及,很明顯,好的東西你能看得見,龜叔早就看見了,龜叔在python3中將str直接存成unicode,我們定義一個str,無需加u前綴,就是一個unicode,屌不屌?

 

二 在python3 中也有兩種字符串類型str和bytes

str是unicode

復制代碼
#coding:gbk
x='' #當程序執行時,無需加u,'上'也會被以unicode形式保存新的內存空間中,

print(type(x)) #<class 'str'>

#x可以直接encode成任意編碼格式
print(x.encode('gbk')) #b'\xc9\xcf'
print(type(x.encode('gbk'))) #<class 'bytes'>
復制代碼

很重要的一點是:看到python3中x.encode('gbk') 的結果\xc9\xcf正是python2中的str類型的值,而在python3是bytes類型,在python2中則是str類型

於是我有一個大膽的推測:python2中的str類型就是python3的bytes類型,於是我查看python2的str()源碼,發現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM