1. 程式人生 > 實用技巧 >XJOI contest 1585 【StormWind】

XJOI contest 1585 【StormWind】

字元編碼

談字元編碼,首先談談和字元相關的知識

1. 預備知識

1.1 python的字元相關

1.1.1 字串

我們之前就瞭解並學習過字串就是由一串字元組合形成的,毋庸置疑談字元編碼肯定和字串有關

1.1.2 文字檔案

在我們沒有接觸python之前,你可能就已經接觸了word檔案,尤其是在學生時代,各種通知,作業都是word

word檔案本質上就是文字檔案,由很多字元組成,只不過當初的你把它叫做漢字或者英文字母。

1.2 三大核心硬體

首先所有的軟體都是執行在硬體之上的,與執行軟體相關的三大核心硬體分別為cpu,記憶體,硬碟。

首先明確:

  1. 軟體執行之前,軟體的程式碼以及相關的資料都是儲存在硬碟上的

  2. 任何軟體的執行都是把資料從硬碟中讀入到記憶體,然後cpu從記憶體中取出指令執行

  3. 軟體執行過程中產生的資料都是先儲存在記憶體中的,如果想要永久儲存資料,則需要將資料由記憶體

    寫入到硬碟中

1.2.2 文字編輯器讀取檔案內容的流程

#階段1、啟動一個檔案編輯器(文字編輯器如nodepad++,pycharm,word)

#階段2、檔案編輯器會將檔案內容從硬碟讀入記憶體

#階段3、文字編輯器會將剛剛讀入記憶體中的內容顯示到螢幕上

1.2.3 python直譯器執行檔案的流程

以python test.py為例,執行流程如下

#階段1、啟動python直譯器,此時就相當於啟動了一個文字編輯器

#階段2、python直譯器相當於文字編輯器,從硬碟上將test.py的內容讀入到記憶體中

#階段3、python直譯器解釋執行剛剛讀入的記憶體的內容,開始識別python語法

1.2.4 總結

python直譯器與檔案本編輯的異同如下

#1、相同點:前兩個階段二者完全一致,都是將硬碟中檔案的內容讀入記憶體,詳解如下
python直譯器是解釋執行檔案內容的,因而python直譯器具備讀py檔案的功能,這一點與文字編輯器一樣

#2、不同點:在階段3時,針對記憶體中讀入的內容處理方式不同,詳解如下
文字編輯器將檔案內容讀入記憶體後,是為了顯示或者編輯,根本不去理會python的語法,而python直譯器將檔案內容讀入記憶體後,可不是為了給你瞅一眼python程式碼寫的啥,而是為了執行python程式碼、會識別python語法)

2. 字元編碼介紹

2.1 什麼是字元編碼

當我們在和計算機進行互動時,用的全都是字元,就是我們人類都夠認識的字元,比如中文字元,英文字元等

但是我們都知道計算機只認識0101這種二進位制數字。

那麼我們在打字,編輯文件的時候,一定會經歷一個過程就是把我們人類能夠認識的字元轉化成010101數字

翻譯的時候肯定必須參照一個標準,該標準稱之為字元編碼表,該表上存放的就是字元與數字一 一對應的關係。

即人類字元和數字的對應關係的一張表,就是字元編碼表。編碼就是按照字元編碼表翻譯的意思,

2.2 解釋一下你看到的字

假如你敲了一個牛叉,計算機會參照字元編碼表,把牛叉翻譯成0101這種二進位制數字存到記憶體中,然後你的文字

編輯器又從記憶體中把這串二進位制數字從記憶體中取了出來,按照同一張字元編碼表反解成對應的字元,顯示出來。

由於這個過程太快了,你根本感知不到。

2.3 字元編碼表的發展史

首先計算機是美國人發明的,所以當初設定第一張對照表的是美國人,且只有美國人認識的英文字元。

2.3.1 第一張字元編碼表——ASCII對照表

ASCII對照表的特點:

  1. 只有英文字元與數字的一一對應關係
  2. 一個英文字元對應1Bytes,1Bytes=8bit(位),8bit最多包含256個數字,可以對應256個字元,足夠表示所有英文字元

注意:英文字元不等同英文字母,字元包括:大小寫字母,標點符號,數字,空白。

2.3.2 各種國家的編碼表

雖然電腦是美國人發明的,但是隨著歷史的發展,各個國家的人們都開始接觸,並想使用電腦。

那麼就要提前解決一個問題——就是原本的ASCII對照吧表中沒有自己國家語言的字元。

於是各個國家開始在ASCII編碼表的基礎上編制自己國家語言的字元編碼表。

gbk(國編碼)

GBK表的特點:

  1. 只有中文字元、英文字元與數字的一一對應關係

  2. 一個英文字元對應1Bytes

    一箇中文字元對應2Bytes

補充說明: 1Bytes=8bit,8bit最多包含256個數字,可以對應256個字元,足夠表示所有英文字元

​ 2Bytes=16bit,16bit最多包含65536個數字,可以對應65536個字元,足夠表示所有中文字元

​ 實際上英文字元+中文字元+韓文字元+日文字元估計也就才6000多個,但是在gbk編碼表中只

​ 有英文+中文字元和數字的對應關係。

Shift_JIS

日文編碼表,只有日文和英文字元與數字的一一對應關係

Euc-kr

韓文編碼表,只有韓文和英文字元與數字的一一對應關係

在那個年代,美國人使用的計算機裡面字元編碼的標準是ASCII,中國人的是GBK,日本人的是Shift_JIS,

韓國人的是Euc-kr。

在他們的電腦上處理文字檔案都是不會出現亂碼的,因為在他們的電腦上只有一個標準,無論是存

檔案,還是取檔案都按照同一張對照表。

文字檔案的存取原理

# 以ASCII對照表為例

# 1.存文字檔案
	人類通過文字編輯器輸入的字元,計算機會按照ASCII對照表翻譯成對應的二進位制存放在記憶體
    如果想要永久儲存,則直接將記憶體的二進位制寫入硬碟即可
    
# 2.取文字檔案
    將存放在硬碟中的ASCII格式的二進位制讀到記憶體中,然後按照ASCII對照表反解成對應的字元

2.3.3 萬國統一 —— unicode

在此之前各國的計算機上還只是只能輸入英文字元+本國字元,但是“貪婪”的我們怎麼能甘心這樣,最好的

方式應該是我的計算機能夠輸入各種國家的字元,而且保證不亂碼。於是unicode(萬國碼)就應運而生了。

unicode於1990年開始研發,1994年正式公佈,具備兩大特點:

  1. 固定一個字元對應2Bytes,部分生僻字用4個Bytes

  2. 存在所有語言中的所有字元與數字的一 一對應關係,即相容萬國字元。

  3. 與傳統的字元編碼的二進位制數都有對應關係,詳解如下

    很多地方或老的系統、應用軟體仍會採用各種各樣傳統的編碼,這是歷史遺留問題。此處需要強調:軟體是存放於硬碟的,而執行軟體是要將軟體載入到記憶體的,面對硬碟中存放的各種傳統編碼的軟體,想讓我們的計算機能夠將它們全都正常執行而不出現亂碼,記憶體中必須有一種相容萬國的編碼,並且該編碼需要與其他編碼有相對應的對映/轉換關係,這就是unicode的第二大特點產生的緣由。
    

比如這個“上”字元,他的unicode對應的數字(4E0A,這裡是16進位制顯示的),不但對應這個上,還對應這

G0-494F,J0-3E45等,也就是unicode第二個特點與傳統二進位制數也有對應關係。

文字編輯器輸入任何字元都是最新存在於記憶體中,是unicode編碼的,存放於硬碟中,則可以轉換成任意其他編

碼,只要該編碼可以支援相應的字元。

# 英文字元可以被ASCII識別
英文字元--->unciode格式的數字--->ASCII格式的數字

# 中文字元、英文字元可以被GBK識別
中文字元、英文字元--->unicode格式的數字--->gbk格式的數字

# 日文字元、英文字元可以被shift-JIS識別
日文字元、英文字元--->unicode格式的數字--->shift-JIS格式的數字

目前所有多的電腦在記憶體中字元的編碼都是unicode,且不能修改,這一點保證了你可以在電腦上輸入任何國家的

字元和你可以開啟以前老編碼的文字檔案,因為他們也和unicode有對應關係,也能反解成對應的字元顯示在螢幕

上。

這個時候已經可以在自己的計算機上輸入萬國的字元了,但是如果你存到硬碟上也採用unicode格式的話,如果絕

大多數字符都是英文字元的話就會造成一種空間的浪費,相對於以前的一個英文字元對應一個Bytes也是一種倒退

雖然說現在儲存已經不值錢了,但是當我們寫進硬碟時,資料量大的一定比小的帶來的IO延遲要高,甚至當我們

由記憶體寫入硬碟時會額外耗費一倍的時間。所以這才是用unicode編碼的檔案不能直接丟到硬碟的原因。所以將內

存中的unicode二進位制寫入硬碟或者基於網路傳輸時必須將其轉換成一種精簡的格式,這種格式即utf-8。

2.3.4 UTF-8

utf-8 全稱Unicode Transformation Format,即unicode的轉換格式

由於unicode的不完美,於是又出現了一種新的字元編碼,就是UTF-8,實質上還是unicode,只不過在中英文字

符上做上了區分。這樣就完美的解決了unicode帶來的儲存問題。

  1. 英文字元對應一個Bytes
  2. 中文字元對應3個Bytes

那為何在記憶體中不直接使用utf-8呢?

utf-8是針對Unicode的可變長度字元編碼:一個英文字元佔1Bytes,一箇中文字元佔3Bytes,生僻字用更多的

Bytes儲存。

unicode更像是一個過渡版本與之前老的編碼格式有這對應關係,我們新開發的軟體或檔案存入硬碟都採用utf-8

格式,等過去幾十年,所有老編碼的檔案都淘汰掉之後,會出現一個令人開心的場景,即硬盤裡放的都是utf-8格

式,此時unicode便可以退出歷史舞臺,記憶體裡也改用utf-8,天下重新歸於統一。

2.4 編碼和解碼

由字元轉換成記憶體中的unicode,以及由unicode轉換成其他編碼的過程,都稱為編碼encode

由記憶體中的unicode轉換成字元,以及由其他編碼轉換成unicode的過程,都稱為解碼decode

在諸多檔案型別中,只有文字檔案的記憶體是由字元組成的,因而文字檔案的存取也涉及到字元編碼的問題

3. 字元編碼的應用

通過上面上面的知識我們已經對字元編碼的理論知識學習完畢了。知道了記憶體中固定使用unicode無論輸入任何字

符都不會發生亂碼。我們能夠修改的是存/取硬碟的編碼方式,如果編碼設定不正確將會出現亂碼問題。亂碼問題

分為兩種:

存亂了

就是你輸入的字元和你即將存到硬碟所採用的編碼方式沒有對應關係.
比如: 你輸入的是日文,但是你儲存檔案的時候選擇GBK,那麼計算機就沒有辦法通過日文對應的unicode碼
     再轉換成gbk格式的二進位制數字存到硬碟中.因為gbk只和英文字元和中文字元有對應關係.
     這個時候計算機不知道怎麼辦了,就隨便對應就會出現亂碼.

讀亂了

就是當你用一個文字編輯軟體開啟一個文字檔案時,原本存放的檔案的編碼如果是gbk格式的二進位制,但是你要
用Shift_JIS開啟的話,對應的二進位制數字是沒有對應的字元的,這個時候計算機不知道怎麼辦了,就隨便對應就會出現亂碼.

讀亂了還好,至少資料是不會丟失的,但是如果你是存亂了,那你原本資料就沒辦法恢復了。

總結

#1. 保證存的時候不亂:在由記憶體寫入硬碟時,必須將編碼格式設定為支援所輸入字元的編碼格式
#2. 保證讀的時候不亂:在由硬碟讀入記憶體時,必須採用與寫入硬碟時相同的編碼格式

3.1 文字編輯器nodpad++存取文字檔案

發現選擇日文編碼就能正常顯示了。

3.2 python程式執行不亂碼

首先在你執行python程式的前提是,你的python程式已經開發好了。然後拿著程式交給python直譯器進行執行

我們知道python程式就相當於文字檔案,python直譯器就相當於文字編輯器。那麼當它讀取檔案時,肯定有自己

的預設編碼。

python3讀取檔案的預設編碼:utf-8

python2讀取檔案的預設編碼:ASCII

但是如果我們的程式的原始檔的編碼是gbk,無論是用python2執行,還是python3執行都會出現讀取亂碼。

這一點開發人員早已想到,可以指定檔案頭修改預設的編碼。在首行新增# coding:gbk(檔案儲存的編碼)

告訴直譯器不要用你的預設編碼讀檔案了,用我指定的就行了。

那麼首行的字元是以什麼編碼讀取的呢?

沒錯就是python直譯器的預設編碼,因為首行全是英文字元,無論哪種編碼都能保證讀取出來。

保證python程式前兩個階段不亂碼的核心法則:

指定檔案頭,# coding:檔案當初存入硬碟時所採用的編碼格式

python3的str型別在記憶體中預設直接存成unicode格式,無論如何都不會亂碼。但是python2不是,因為

python1989年誕生,但是unicode1990年才開始推行。所以你懂的。要想保證python2的str型別不亂碼

在字串前+u,例如:x = u"大帥比",強制告訴python2直譯器用unicode格式存。

記憶體的編碼使用unicode,不代表記憶體中全都是unicode,

在程式執行之前,記憶體中確實都是unicode,比如從檔案中讀取了一行x="ymn",其中的x,等號,引號,地位都一樣,都是普通字元而已,都是以unicode的格式存放於記憶體中的

但是程式在執行過程中,會申請記憶體(與程式程式碼所存在的記憶體是倆個空間)用來存放python的資料型別的值,而python的字串型別又涉及到了字元的概念

比如x="ymn",會被python直譯器識別為字串,會申請記憶體空間來存放字串型別的值,至於該字串型別的值被識別成何種編碼存放,這就與python直譯器的有關了,而python2與python3的字串型別又有所不同。

4. 補充

4.1 python2的兩種字串型別

python2直譯器有兩種字串型別:str,unicode

4.1.1 str

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

# 字串值會按照檔案頭指定的編碼格式存入變數值的記憶體空間
# 不指定就是預設的編碼格式(ASCII)

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

4.1.2 unicode

當python直譯器執行到產生字串的程式碼時(例如s=u'袁'),會申請新的記憶體地址,然後將'袁'以unicode的格

式存放到新的記憶體空間中,所以s只能encode,不能decode

# python2
x = u"上"

# 強制存成unicode

4.2 列印到終端

對於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,屌不屌?

4.3 python3的兩種字串型別

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()原始碼,發現