Python 入門 26 —— ASCII 編碼、Unicode 編碼、 UTF-32、 UTF-16、 UTF-8、 GB2312 編碼、 GBK 編碼

計算機存儲和處理信息都是以一個8位的二進制字節爲單位的,例如:0b 1111 0000。一個字母、漢字等如何用一個二進制的數(編碼)來表示呢。在計算機發展初期,因爲沒有人能預料到計算機會有現在這麼大的發展,也沒有想到要處理全世界的字符,所以,在發展之初僅設計了一個簡單的、能表示128個字符的編碼方案————ASCII編碼 。

一、ASCII 編碼

ASCII 編碼方案規定,在一個8位的二進制字節中,第1位(最高位)固定爲0,然後其它7位不斷變化,以表示26個大寫、26小寫的英文字母、10個數字和其它的常用符號。例如:

A ———— 0b 0100 0001、或:0x 41、或:65
B ———— 0b 0100 0010、或:0x 42、或:66
C ———— 0b 0100 0011、或:0x 43、或:67
a ———— 0110 0001、或:0x61、或:97
b ———— 0110 0010、或:0x62、或:98
c ———— 0110 0011、或:0x63、或:99
1 ———— 0011 0001、或:0x31、或:49
2 ———— 0011 0010、或:0x32、或:50
3 ———— 0011 0011、或:0x33、或:51
小於號 < ———— 0011 1100、或:0x3C、或:60
等號 = ———— 0011 1101、或:0x3D、或:61
大於 > ———— 0011 11100、或:x3E、或:62
回車鍵 ———— 0000 1101、或:0x0D、或:13
響鈴 ———— 0000 0111、或:0x07、或:7

ASCII編碼中,第1位固定爲0,只有剩餘7位可以進行各種變化,所以,最多可表示128個字符。

爲了本文後面敘述的方面,我個人在這篇文章中,暫且將ASCII編碼中這128個字符稱作:原符。也就是說,我在本篇下文中所稱的原符,就是指ASCII編碼中這128個字符。

二、Unicode 編碼

隨着計算機技術的發展,原符顯然遠遠不夠。於是就有了國際上統一的 Unicode 編碼,它爲全世界所有字符都分配了一個唯一的編碼。當然,現在我們看到的 Unicode 編碼也是從簡到繁不斷完善形成的。

Unicode 編碼規定,用一個21位的二進制數表示一個字符。即:

第1個:0b 0 0000 0000 0000 0000 0000、或:0x 00 0000
第2個:0b 0 0000 0000 0000 0000 0001、或:0x 00 0001
......
倒 二:0b 1 0000 1111 1111 1111 1110、或:0x 10 fffe
最 後:0b 1 0000 1111 1111 1111 1111、或:0x 10 ffff

實際上,Unicode 編碼是將所有字符分成17個種類(17個面)進行分類編碼:

0x 00 0000 ———— 00 ffff:第1面,基本多文種平面(BMP):原符、各國文字等
0x 01 0000 ———— 01 ffff:第2面,多文種補充平面
0x 02 0000 ———— 02 ffff:第3面,表意文字補充平面
0x 03 0000 ———— 03 ffff:第4面,表意文字第三平面
0x 04 0000 ———— 04 ffff:第5面,未使用
......
0x 0d 0000 ———— 0d ffff:第14面,未使用
0x 0e 0000 ———— 0e ffff:第15面,特別用途補充平面
0x 0f 0000 ———— 0f ffff:第16面,專用區-A
0x 10 0000 ———— 10 ffff:第17面,專用區-B

其中 0x 00 4E00 ---- 00 9FFF 是中、日、韓的三種文字,例如:

一 ———— 0x 00 4e00 
習 ———— 0x 00 4e60
鄉 ———— 0x 00 4e61
笑 ———— 0x 00 7b11
大 ———— 0x 00 5927
好 ———— 0x 00 597d

從理論計算,0x 00 0000 ———— 10 ffff,可以有1,114,112種編碼,但世界所有的字符顯然比這少的多,所以,有許多的編碼是空的,沒有被使用。

Unicode 編碼是一個字符的編碼方案,它確保了世界上的每一個符號都有一個唯一的編碼。Unicode 編碼一般用“U + 16進制編碼值”的形式表示。例如:

A ———— U+41
B ———— U+42
C ———— U+43
一 ———— U+4e00
習 ———— U+4e60
鄉 ———— U+4e61

一個Unicode 編碼是一個21位的二進制數,從表面上看,在計算機中用3個字節來存儲一個Unicode編碼正合適,但計算通常不用3個字節作爲一個單位,所以,用4個字節來存儲一個Unicode 編碼應該是最好的。編碼問題本該到此結束,但實際上並非如此。

儘管Unicode 編碼設計了17個面,所實際上僅第一個面被充分利用,其它面很多是空的。也就是說,絕大部分全世界常用的字符都在第一個面,即,絕大部分全世界常用的字符的Unicode 編碼前面都是一樣的 ———— 全是0,這樣,用4個字節來存儲一個Unicode 編碼就顯得很“浪費”,例如:

A ———— 0b 0000 0000 0000 0000 0000 0000 0100 0001、U+41
B ———— 0b 0000 0000 0000 0000 0000 0000 0100 0010、U+42
C ———— 0b 0000 0000 0000 0000 0000 0000 0100 0011、U+43
一 ———— 0b 0000 0000 0000 0000 0100 1110 0000 0000、U+4e00
習 ———— 0b 0000 0000 0000 0000  0100 1110 0110 0000、U+4e60
鄉 ———— 0b 0000 0000 0000 0000  0100 1110 0110 0001、U+4e61

目前,在計算機領域 Unicode 編碼方面主要有三種存儲規則:UTF-32、UTF-16、UTF-8。

特別要注意的是,編碼和編碼的存儲規則不是一回事,編碼是解決一個字符用哪些數字來表示的問題,同一個字符在不同的編碼方案會有不同的“碼”,ASCII 編碼、Unicode 編碼等,都屬於編碼方案。除了這兩種最出名的編碼方案,世界上還有許許多多其它的編碼方案。編碼的存儲規則是解決一個編碼如何存儲的問題。例如,我國的GB2312編碼、GBK編碼。如果不需要考慮優化,存儲規則可以很簡單的:編碼有多大就用多大的單元來存儲,但這樣會“浪費”很多空間,也不利於提高存取的效率。於是就有了各種優化方法,即,各種存儲規則。UTF-32、UTF-16、UTF-8 等都屬於編碼的存儲規則。

不過,我們有時也確實能看到“UTF-32編碼”、“UTF-16編碼”、“UTF-8編碼”這樣的說法。這是因爲Unicode 編碼按照不同的規則存儲後,在存儲單元中的實際樣子有時與Unicode 編碼有較大的區別(特別是 UTF-8),爲了方便稱呼 Unicode 編碼在實際存儲後的樣子,我們也就分別稱它們爲 UTF-32編碼、UTF-16編碼、UTF-8編碼。

例如:“笑”字的Unicode 編碼爲:U+7b11,按不同規則存儲後的樣子如下:

UTF-32(用4個字節單元存儲):0000 0000 0000 0000 0111 1011 0001 0001 ———— 0x 00007b91
UTF-16(用2個字節單元存儲):0111 1011 0001 0001 ———— 0x 7b91
UTF-8(用3個字節單元存儲):1110 0111 1010 1100 1001 0001 ———— 0x e7ac91

於是稱:“笑”字的 UTF-32編碼 0x 00007b91,UTF-16編碼 0x 7b91,UTF-8編碼 0x e7ac91 。

由此可見, UTF-32編碼、UTF-16編碼、UTF-8編碼實際上都是 Unicode 編碼的“異形”。

三、UTF-32

這個規則最簡單,那就是直接用4個字節來表示一個Unicode 編碼。當然,如上所述,這個規則會“浪費”很多的存儲空間,存取的效率是最低的。

用4個字節來表示一個Unicode 編碼還存在一個“大端、小端”的問題。一個編碼有4字節長,也就是在存儲器上要佔4個存儲單元。計算機中每個存儲單元都有一個地址,地址從小到大有高低之分,Unicode 編碼的4字節,是從低地址的單元往高地址的單元存放呢,還是反過來,這就是大、小端的問題。將高位字節放到低地址存儲單元,是大端法;反之,將高位字節放到高地址存儲單元,是小端法。這有點像我們寫字時,是從左往右寫呢,還是從右往左寫。兩種方法都可以,只不過,怎麼寫就要怎麼讀,二者一定要一致。

四、UTF-16

1、UTF-16 的規則

UTF-16 比 UTF-32 複雜,其存儲規則是:

(1)對於Unicode 編碼在U+0000 到 U+FFFF的字符(常用字符集),直接用2個字節單元表示。

(2)對於Unicode 編碼在 U+10000到U+10FFFF之間的字符,需要用4個字節單元表示。

也就是說,根據UTF-16的規則,有的編碼用2個字節單元存儲,而有的用4個字節單元存儲,那在讀取時,如何識辨這兩種情況呢?

2、替代區

爲了配合 UTF-16 規則,Unicode 編碼在設計時,做了一個特殊的規定:在第一面劃出一個區塊(D800 - DFFF)不分配給任何字符,也就是,不可能有一字符它的編碼在 U+D800 – U+DFFF之間,這部分可稱爲“替代區”,即:

0b 1101 1000 0000 0000 ———— 1101 1011 1111 1111 設置爲“高位替代”區、(0x D800 – DBFF)
0b 1101 1100 0000 0000 ———— 1101 1111 1111 1111 設置爲“低位替代”區、(0x DC00 – DFFF)

從上可見,對於一個16位的二進制碼(2個字節長),如果前6位爲:1101 10 或 1101 11,它就處在了替代區。

3、讀取依據 UTF-16 規則存儲的 Unicode 編碼

首先,一次取2個連續的存儲單元,把它們拼在一起,構成一個16位的二進制數,然後,查看這數的前6位,結果有三種情況:

情況一、不在替代區,即,前6位不是 1101 10 ,也不是 1101 11 ,則認定這是一個用2個字節存儲某字符的Unicode 編碼,反查編碼表,取得字符。例如,取到的二進制數是 0b 0111 1011 0001 0001,前6位是 0111 10,不是 1101 10 ,也不是 1101 11,所以,直接將它看作一個字符的Unicode 編碼,反查編碼表,得出漢字“笑”。

情況二、在高位替代區,即,前6位是 1101 10 ,例如:1101 10xx xxxx xxxx,則認定這是一個用4個字節存儲的某字符的Unicode 編碼,且當前取到是“高位部分”。

(1)緊接着再取2個連續的存儲單元拼在一起,構成一個16位的二進制數,例如:zzzz zzyy yyyy yyyy。

(2)取兩個二進制數的後10位,拼成一個新的數(高位部分的在前),例如:xxxx xxxx xxyy yyyy yyyy。

(3)把這個新拼合成的數加上 0x 10000,將和作爲一個 Unicode 編碼,反查編碼表,取得字符。也就是說,在種情況下,認定取得的 Unicode 編碼爲:

xxxx xxxx xxyy yyyy yyyy + 1 0000 0000 0000 0000

情況三、在低位替代區,即,前6位是 1101 11 ,例如:1101 11xx xxxx xxxx,則認定這也是一個用4個字節存儲的某字符的Unicode 編碼,且當前取到是“低位部分”。

(1)緊接着再取2個連續的存儲單元拼在一起,構成一個16位的二進制數,例如:zzzz zzyy yyyy yyyy。

(2)取兩個二進制數的後10位,拼成一個新的數(低位部分的在後),例如:yyyy yyyy yyxx xxxx xxxx。

(3)把這個新拼合成的數加上 0x 10000,將和作爲一個 Unicode 編碼,反查編碼表,取得字符。也就是說,在種情況下,認定取得的 Unicode 編碼爲:

yyyy yyyy yyxx xxxx xxxx + 1 0000 0000 0000 0000

4、UTF-16 規則存儲Unicode 編碼的具體過程

首先,查看編碼大小,然後分兩種情況存儲:

情況一:編碼在 U+0000 – U+FFFF 之間,則直接用2個字節單元存儲。

情況二:編碼在 U+10000到U+10FFFF 之間,則按以下步驟進行:

(1)將編碼減去 0x 10000,得到一個20位的二進制數,例如:0b xxxx xxxx xxyy yyyy yyyy。

(2)將新得到的20位的二進制數一分爲二,前10位,放在 1101 10 之後,構成一個16位數,例如:

0b 1101 10xx xxxx xxxx

(3)後10位,放在 1101 11 之後,構成另一個16位數,例如:

0b 1101 11yy yyyy yyyy

(4)將兩個16位數拼成一個32位數存入了4個存儲單元中。例如:

0b 1101 10xx xxxx xxxx 1101 11yy yyyy yyyy

很顯然,UTF-16 規則在用兩個單元存儲Unicode 編碼時,也存在“大端、小端”的問題。

5、高位專用替代區

實際上,Unicode 編碼中把“高位替代”區中的 0x DB80 – DBFF 又稱作“高位專用替代”區。因爲,當一個編碼的高位部分落在這一區域時,還原所得的 Unicode 編碼,一定會在第16和第17面,而這兩個面又被稱作專用區。

“高位專用替代”區:0b 1101 1011 1000 0000 ———— 1101 1011 1111 1111、或:0x DB80 – DBFF

例如:所得4個字節組成的二進制數爲:

1101 1011 1xxx xxxx 1101 11yy yyyy yyyy

各取後10位拼合成:111x xxxx xxyy yyyy yyyy

加 0x 10000 :111x xxxx xxyy yyyy yyyy + 1 0000 0000 0000 0000

結果必定爲:1111 xxxx xxyy yyyy yyyy(在16面)或 1 0000 xxxx xxyy yyyy yyyy(在17面)

五、UTF-8

1、UTF-8規則

從表面上看,UTF-8 比 UTF-16 還要簡單一些,它依據各字符的 Unicode編碼的大小來決定用幾個存儲單元來存儲:

(1)U+0 – U+7F:用1個字節單元存儲,首位固定爲0,例如:0xxx xxxx

(2)U+0 – U+7F 之外的用2-4個字節單元存儲。當某符號用n個字節單元存儲時(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10,剩下的沒有提及的二進制位,全部爲這個符號的 Unicode 編碼。

U+80 – U+7FF:2個字節,110x xxxx 10xx xxxx

U+800 – U+FFFF:3個字節,1110 xxxx 10xx xxxx 10xx xxxx

U+10000~U+10FFFF:4個字節, 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx

由於UTF-8的處理單元爲一個字節(也就是一次處理一個字節),所以,不存在“大端、小端”的問題。

2、UTF-8規則與ASCII編碼

爲了與ASCII編碼兼容,Unicode編碼中的前128個編碼全部對應分配給了原符。由於這前128個字符的編碼在 U+0 – U+7F 之間,所以,按照UTF-8規則,它們都是用1個字節單元存儲。也就是說,按照UTF-8規則,原符實際存儲的編碼值、Unicode編碼、ASCII編碼完全相同。

六、GB2312 編碼、GBK 編碼

GB2312 是早期的的中文編碼,它用16位二進制數(2個字節)給原符和漢字進行編碼。

1、原符的GB2312編碼

原符的GB2312編碼形式爲:

0x 0000 0xxxx (第1個字節全爲0,第二個字節首位爲0,碼值與ASCII編碼相同)

2、漢字的GB2312編碼

漢字 GB2312 編碼的兩個字節的分別被稱爲:高位字節 和 低位字節。

“高位字節”使用了0xA1-0xF7,“低位字節”使用了0xA1-0xFE,即:

高位字節:0b 1010 0001 ———— 1111 0111 、 0x A1 – F7

低位字節:0b 1010 0001 ———— 1111 1110 、 0x A1 – FE

GB2312 編碼共收錄 6763 個漢字,也就是,一共給 6763 個漢字編了碼。

GB2312編碼將漢字的2個字節的首位都固定設置爲1,這是爲了與原碼進行區別,很有必要,但除首位之外,還有一些位也被固定了,這一方面是爲了讀取方便,另一方面,是當時的計算機技術不需要用到太多的漢字,所以也就不需要用到所有的位。

隨着技術的發展,GB2312編碼共收錄的漢字不夠用了,於是,在其基礎上開發了GBK編碼。

3、GBK 編碼

GBK編碼也是使用2個字節編碼,原符的GBK編碼沿用原符的GB2312編碼,沒有變化。

漢字的兩個字節被擴展了:

高位字節範圍擴展爲: 0×81-0xFE,低位字節範圍擴展爲: 0x40-7E 和 0x80-0xFE,即:

高位字節:0b 1000 0001 ———— 1111 1110 、 0x 81 – FE

低位字節1:0b 0100 0000 ———— 0111 1110、 0x 40 – 7E
低位字節2:0b 1000 0000 ———— 1111 1110 、0x A1 – FE

從編碼範圍看,在GB2312編碼中有些固定不用的位,在GBK 編碼中被啓用。用的位多了,編碼數量也就增加了。GBK 編碼總計有 23940 個碼位,共收入 21886 個漢字和圖形符號。

GBK 編碼 之後還有一個更大的 GB18030 編碼。它收錄漢字70000餘個,但實際應用範圍並不廣。

4、GB2312(GBK) 編碼的存儲

GB2312(GBK) 編碼存儲時,原符用1個字節單元存儲(首位固定爲0),漢字用2個字節單元存儲。按照GB2312(GBK) 編碼存儲規則,原符實際存儲的編碼值、GB2312(GBK) 編碼、ASCII編碼完全相同。

七、Python 按 UTF-8 規則讀取編碼

不管程序文件用什麼編碼方式存儲,Python 解釋運行程序在讀取文件時,都一律是按照 UTF-8 規則進行讀取。所以,不論用什麼編輯器編寫Python 程序,最好都保存爲 UTF-8 格式。

當程序中沒有漢字時,如果程序文件用GB2312(GBK)編碼存儲,在Python 解釋運行程序讀取運行時不會出錯。因爲,原符不論是以GB2312(GBK)編碼的方式存儲,還是以 UTF-8 規則存儲,其碼值都是一樣,都與它們ASCII編碼完全相同,所以,用GB2312(GBK)編碼方式存儲,用 UTF-8 規則讀取,不會出錯。但是,當程序中有漢字時,如果再用GB2312(GBK)編碼方式存儲,就一定會出錯。

Python 在讀取字符時,不論是一個數字、一個字母,還是一個漢字,都按一個字符對待。例如:

print(len('1234567'))    # 7
print(len('abcdefg'))    # 7
print(len('中華人民共和國'))    # 7
print(len('Python解釋運行程序'))    # 12
print(len('    '))    # 4個空格:4
print(len('--——,:'))    # 6

Python 解釋運行程序是完全支持中文的,只是按 UTF-8 格式存儲,讀取和處理中文一點問題都沒有,甚至可以用漢字來作爲變量名。例如:

= '高興和快樂'= 88= 11= 22
x =+print(,,x)    # 高興和快樂 88 33

———————————————— 本篇完 ————————————————

看完之後,麻煩您順手點擊下方 “點贊” 兩個字給我點個贊吧 ^-^ , 謝謝您了。

如果您還能像我小學一年級班主任好老師那樣,給我隨心寫上幾句表揚或批評的話語,那真是感激不盡!

在我人生的道路上,有了您的鼓勵和指導,我一定成長快快。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章