熟悉計(jì)算機(jī)的同學(xué)一定聽(tīng)說(shuō)過(guò)編碼字符集這么個(gè)名詞。utf-8作為當(dāng)下被廣泛應(yīng)用的編碼字符集净神,大家也一定有所耳聞何吝。
對(duì)于電子計(jì)算機(jī)而言,處理器只能識(shí)別二進(jìn)制機(jī)器碼鹃唯。所以任何人類(lèi)可以進(jìn)行閱讀的電腦文件爱榕,其實(shí)只是一系列0和1的組合。是字符編碼規(guī)則坡慌,將機(jī)器可以識(shí)別的二進(jìn)制內(nèi)容轉(zhuǎn)換為人類(lèi)可以識(shí)別的各種文檔黔酥。
最早的編碼字符集ACSII,使用1字節(jié)表示128個(gè)字符八匠,一個(gè)字節(jié)是8位絮爷,也就是從0~127(二進(jìn)制區(qū)間為00000000~01111111),每一個(gè)數(shù)值都對(duì)應(yīng)著一個(gè)字符梨树。但是ASCII是為英語(yǔ)設(shè)計(jì)的,不能表示諸如漢語(yǔ)等其他語(yǔ)言的字符岖寞。
針對(duì)這個(gè)問(wèn)題抡四,大家可能會(huì)覺(jué)得,我們只要使用更多的字節(jié)仗谆,就可以有更大的整數(shù)范圍去和世界上所有的字符一一對(duì)應(yīng)指巡。這當(dāng)然是一種解決問(wèn)題的辦法,然而單純的擴(kuò)大字節(jié)數(shù)是有弊端的隶垮。假設(shè)我們可以使用4個(gè)字節(jié)表示所有字符藻雪,文件中每個(gè)字符都使用4個(gè)字節(jié)進(jìn)行表示,結(jié)果原本ASCII中僅需要一個(gè)字節(jié)就能表示的字符也占用了4個(gè)字節(jié)的空間狸吞,這就造成了極大的浪費(fèi)勉耀,要知道早期計(jì)算機(jī)的存儲(chǔ)設(shè)備不僅價(jià)格高昂而且容量小指煎。于是變長(zhǎng)編碼字符集應(yīng)運(yùn)而生,我們今天常見(jiàn)的utf-8就是變長(zhǎng)編碼字符集的一種便斥。
utf-8字符集與ACSII字符集是兼容的至壤,也就是說(shuō)可以被ASCII正常解讀的文件,可以被utf-8正常解讀枢纠,而且結(jié)果一致像街。那么utf-8究竟是怎么進(jìn)行編碼的呢?
我們發(fā)現(xiàn)ACSII雖然使用了一個(gè)字節(jié)來(lái)表示字符晋渺,但是最高位是沒(méi)有使用的镰绎。utf-8也使用每個(gè)字節(jié)的頭幾位來(lái)記錄關(guān)于字符編碼的信息。
例如對(duì)于漢字而言木西,utf-8需要3個(gè)字節(jié)來(lái)進(jìn)行表示跟狱,一般長(zhǎng)這個(gè)樣子:1110xxxx 10xxxxxx 10xxxxxx。對(duì)于第一個(gè)字節(jié)户魏,使用二進(jìn)制111表示這個(gè)字符需要3個(gè)字節(jié)驶臊,再加上一個(gè)0與實(shí)際要記錄的值隔開(kāi),最后4位是該字符實(shí)際二進(jìn)制值的最高4位叼丑。對(duì)于第二第三個(gè)字節(jié)关翎,最高兩位使用10占位,剩下的6位分別是該字符實(shí)際二進(jìn)制值的中間6位和最后6位鸠信。
與上面的表達(dá)方式類(lèi)似的纵寝,2個(gè)字節(jié)表示的字符,在二進(jìn)制下表示為:110xxxxx 10xxxxxx星立;4個(gè)字節(jié)表示的字符爽茴,在二進(jìn)制下表示為:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;5個(gè)字節(jié)表示的字符绰垂,在二進(jìn)制下表示為:111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx室奏;6個(gè)字節(jié)表示的字符,在二進(jìn)制下表示為:1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx劲装。
實(shí)際上對(duì)于我們平時(shí)使用而言胧沫,4個(gè)字節(jié)已經(jīng)足夠了(即除ACSII字符外,還可以表示2^21-1個(gè)字符占业,約200萬(wàn)個(gè)字符)绒怨。而且使用超過(guò)4個(gè)字節(jié)表示字符也是不合utf-8規(guī)范的。
當(dāng)計(jì)算機(jī)根據(jù)utf-8編碼解碼文件時(shí)谦疾,如果遇到一個(gè)字節(jié)是以10開(kāi)頭的南蹂,那么立刻可以知道這不是一個(gè)字符的開(kāi)頭。通過(guò)向前或向后查找以110念恍、1110六剥、11110開(kāi)頭的字節(jié)開(kāi)始進(jìn)行解碼晚顷。而當(dāng)遇到以0開(kāi)頭的字節(jié)時(shí),自然按照ACSII碼來(lái)進(jìn)行處理仗考。
下面我使用go語(yǔ)言編寫(xiě)一段代碼音同,示例utf-8的編碼方式:
```go
s :=`中a`
fmt.Println("字節(jié)長(zhǎng)度",len(s))
for i :=0;i
fmt.Printf("十進(jìn)制字節(jié)值: %d,二進(jìn)制字節(jié)值: %08b\n",s[i],s[i])
}
fmt.Println("=======")
for i,r :=range s {
fmt.Printf("字符在串中的位置: %d,字符: %c,二進(jìn)制碼: %08b\n",i,r,r)
}
```
輸出結(jié)果為:
字節(jié)長(zhǎng)度 4
十進(jìn)制字節(jié)值: 228,二進(jìn)制字節(jié)值: 11100100
十進(jìn)制字節(jié)值: 184,二進(jìn)制字節(jié)值: 10111000
十進(jìn)制字節(jié)值: 173,二進(jìn)制字節(jié)值: 10101101
十進(jìn)制字節(jié)值: 97,二進(jìn)制字節(jié)值: 01100001
=======
字符在串中的位置: 0,字符: 中,二進(jìn)制碼: 100111000101101
字符在串中的位置: 3,字符: a,二進(jìn)制碼: 01100001
結(jié)果印證了上文中的描述。