編碼和解碼的問題糾結(jié)了我很久了拘哨,對(duì)他一直只有是是而非的理解,好像是那么回事信峻,但是又不懂倦青,今天終于來認(rèn)真解決一下這個(gè)問題,總結(jié)一下大神們的回答盹舞,做一下筆記产镐。
首先,我們知道踢步,計(jì)算機(jī)中的所有數(shù)據(jù)都以二進(jìn)制存在癣亚,二進(jìn)制的一位有兩種不同的狀態(tài)0和1。一個(gè)字節(jié)(Byte)由8位二進(jìn)制數(shù)組成获印,可以用來表示256種不同的狀態(tài)述雾。將二進(jìn)制序列與字符一一對(duì)應(yīng)的過程就叫做編碼規(guī)則。
一兼丰、ASCII 碼
世界上最著名的編碼規(guī)則是上世紀(jì)60年代美國制定的ASCII 碼玻孟,它將英語字符與二進(jìn)制位之間的關(guān)系做了統(tǒng)一規(guī)定。ASCII 碼規(guī)定一個(gè)字節(jié)的最高位總是0鳍征,用剩余的7位來表示英語中需要的128個(gè)字符(包括不能打出來的32個(gè)控制位)黍翎,比如空格SPACE是32(二進(jìn)制00100000)。
二艳丛、非ASCII編碼
英語用128個(gè)符號(hào)編碼就夠了匣掸,但是用來表示其他語言,128個(gè)符號(hào)是不夠的氮双。
比如旺聚,在法語中,字母上方有注音符號(hào)眶蕉,它就無法用 ASCII 碼表示砰粹。于是,一些歐洲國家就決定,利用字節(jié)中閑置的最高位編入新的符號(hào)碱璃。比如弄痹,法語中的é的編碼為130(二進(jìn)制10000010)。這樣一來嵌器,這些歐洲國家使用的編碼體系肛真,可以表示最多256個(gè)符號(hào)。
但是爽航,這里又出現(xiàn)了新的問題蚓让。不同的國家有不同的字母,因此讥珍,哪怕它們都使用256個(gè)符號(hào)的編碼方式历极,代表的字母卻不一樣。比如衷佃,130在法語編碼中代表了é趟卸,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會(huì)代表另一個(gè)符號(hào)氏义。但是不管怎樣锄列,所有這些編碼方式中,0--127表示的符號(hào)是一樣的惯悠,不一樣的只是128--255的這一段邻邮。
至于亞洲國家的文字,使用的符號(hào)就更多了克婶,漢字就多達(dá)10萬左右筒严。一個(gè)字節(jié)只能表示256種符號(hào),肯定是不夠的鸠补,就必須使用多個(gè)字節(jié)表達(dá)一個(gè)符號(hào)萝风。
世界上存在著這么多種類的編碼方式嘀掸,同一個(gè)二進(jìn)制數(shù)字可以被解釋成不同的符號(hào)紫岩。因此,要想打開一個(gè)文本文件睬塌,就必須知道它的編碼方式泉蝌,否則用錯(cuò)誤的編碼方式解讀,就會(huì)出現(xiàn)亂碼揩晴。
如果有一種編碼勋陪,將世界上所有的符號(hào)都納入其中。每一個(gè)符號(hào)都給予一個(gè)獨(dú)一無二的編碼硫兰,就能夠解決文件亂碼的問題诅愚,Unicode應(yīng)運(yùn)而生。
三劫映、Unicode和UTF-8
準(zhǔn)確的說违孝,Unicode不是一種編碼刹前,而是一個(gè)字符集。在Unicode字符集的基礎(chǔ)上產(chǎn)生的UTF-8雌桑、UTF-16和UTF-32才是真正的編碼規(guī)則喇喉。
Unicode字符集
Unicode 當(dāng)然是一個(gè)很大的集合,現(xiàn)在的規(guī)男?樱可以容納100多萬個(gè)符號(hào)拣技。每個(gè)符號(hào)的編碼都不一樣,U+0041表示英語的大寫字母A耍目,U+4E25表示漢字嚴(yán)膏斤。
比如,漢字嚴(yán)的 Unicode 是十六進(jìn)制數(shù)4E25制妄,轉(zhuǎn)換成二進(jìn)制數(shù)足足有15位(100111000100101)掸绞,也就是說,這個(gè)符號(hào)的表示至少需要2個(gè)字節(jié)耕捞。表示其他更大的符號(hào)衔掸,可能需要3個(gè)字節(jié)或者4個(gè)字節(jié),甚至更多俺抽。
這里就有兩個(gè)嚴(yán)重的問題敞映,第一個(gè)問題是,如何才能區(qū)別 Unicode 和 ASCII 磷斧?計(jì)算機(jī)怎么知道三個(gè)字節(jié)表示一個(gè)符號(hào)振愿,而不是分別表示三個(gè)符號(hào)呢?第二個(gè)問題是弛饭,我們已經(jīng)知道冕末,英文字母只用一個(gè)字節(jié)表示就夠了,如果 Unicode 統(tǒng)一規(guī)定侣颂,每個(gè)符號(hào)用三個(gè)或四個(gè)字節(jié)表示档桃,那么每個(gè)英文字母前都必然有二到三個(gè)字節(jié)是0,這對(duì)于存儲(chǔ)來說是極大的浪費(fèi)憔晒,文本文件的大小會(huì)因此大出二三倍藻肄,這是無法接受的。
它們?cè)斐傻慕Y(jié)果是:
- 出現(xiàn)了 對(duì)應(yīng)Unicode 字符集的多種編碼方式拒担,比如UTF-8嘹屯、UTF-16和UTF-32。
- Unicode 在很長一段時(shí)間內(nèi)無法推廣从撼,直到互聯(lián)網(wǎng)的出現(xiàn)州弟。
UTF-8
互聯(lián)網(wǎng)的普及,強(qiáng)烈要求出現(xiàn)一種統(tǒng)一的編碼方式。UTF-8 就是在互聯(lián)網(wǎng)上使用最廣的一種 Unicode 的實(shí)現(xiàn)方式婆翔。
UTF-8 最大的一個(gè)特點(diǎn)桐经,就是它是一種變長的編碼方式。它可以使用1~4個(gè)字節(jié)表示一個(gè)符號(hào)浙滤,根據(jù)不同的符號(hào)而變化字節(jié)長度阴挣。
UTF-8 的編碼規(guī)則很簡單,只有二條:
1)對(duì)于單字節(jié)的符號(hào)纺腊,字節(jié)的第一位設(shè)為0畔咧,后面7位為這個(gè)符號(hào)的 Unicode 碼。因此對(duì)于英語字母揖膜,UTF-8 編碼和 ASCII 碼是相同的誓沸。
2)對(duì)于n字節(jié)的符號(hào)(n > 1),第一個(gè)字節(jié)的前n位都設(shè)為1壹粟,第n + 1位設(shè)為0拜隧,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位趁仙,全部為這個(gè)符號(hào)的 Unicode 碼洪添。
下表總結(jié)了編碼規(guī)則,字母x表示可用編碼的位雀费。
跟據(jù)上表干奢,解讀 UTF-8 編碼非常簡單。如果一個(gè)字節(jié)的第一位是0盏袄,則這個(gè)字節(jié)單獨(dú)就是一個(gè)字符忿峻;如果第一位是1,則連續(xù)有多少個(gè)1辕羽,就表示當(dāng)前字符占用多少個(gè)字節(jié)逛尚。
可以看到,UTF-8是兼容了ASCII碼的一種編碼方式刁愿。
四绰寞、Python中的編解碼
編碼(encode()):將字符轉(zhuǎn)化為計(jì)算機(jī)能夠理解,能夠存儲(chǔ)在內(nèi)存或文件中的二進(jìn)制數(shù)據(jù)酌毡。
如在Python中輸入以下代碼:
str = "簡書"
print(str)
print(type(str))
print(str.encode())
print(type(str.encode()))
輸出:
簡書
<class 'str'>
b'\xe7\xae\x80\xe4\xb9\xa6'
<class 'bytes'>
此過程就是將str類型的數(shù)據(jù)轉(zhuǎn)換為了二進(jìn)制克握,為了方便顯示蕾管,通過16進(jìn)制輸出了編碼后的結(jié)果枷踏。可以看到每個(gè)漢字用了六個(gè)16進(jìn)制數(shù)字來表示掰曾,對(duì)應(yīng)著三位Byte堕仔,三位比特就能表示最多16,777,216個(gè)字符了蚣录,能夠表示完所有漢字捞烟。
python3.6在不指定的情況下傲绣,執(zhí)行編碼方式默認(rèn)使用utf-8編碼,因此在print()打印漢字時(shí)镀梭,不需要再指定encoding = 'utf-8',但在之前的版本需要指定。
注意:python中經(jīng)常需要在方法(或函數(shù))中傳輸參數(shù)(encoding='utf-8')這不一定是在對(duì)數(shù)據(jù)進(jìn)行編碼舌胶,這一點(diǎn)要理解,不然會(huì)感覺很矛盾疮丛。這個(gè)參數(shù)是在告訴方法傳入數(shù)據(jù)的編碼方式為UTF-8(或其他方式)幔嫂,方法才能對(duì)數(shù)據(jù)進(jìn)行正確的解碼。
另一個(gè)使用encoding較多的方法是open誊薄,如下所示:
with open('text.txt','w') as file:
file.write('簡書')
由于txt文件對(duì)中文的默認(rèn)編碼方式是GBK履恩,Python使用默認(rèn)編碼方式'UTF-8'打開后就是????這樣的亂碼,同時(shí)報(bào)錯(cuò):
這時(shí)候通過指定編碼方式就能解決呢蔫,通過'utf-8'方式打開并寫入文件:
with open('text.txt','w',encoding='utf-8') as file:
file.write('簡書')
解碼(decode()):將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為能夠閱讀的字符的過程切心。
這個(gè)比較簡單,如上節(jié)通常是指定encoding='utf-8'