最近在開發(fā)上遇到了點(diǎn)編碼的問題绢掰,又重新學(xué)習(xí)了字符集以及編碼的知識(shí)没酣。特此記錄一下舀患。
核心
我比較喜歡一開始講述核心的東西蚓峦,這樣子讀者可以帶著這些知識(shí)往下看舌剂,并且在翻閱到這個(gè)頁(yè)面的時(shí)候可以一眼就可以看到最關(guān)鍵的內(nèi)容。
- Unicode是字符集
- UTF-8是編碼規(guī)則
Unicode
在很久很久之前暑椰,幾個(gè)美國(guó)人發(fā)現(xiàn)用8個(gè)可以開合的晶體管來表示不同的狀態(tài)霍转,即可以表示2^8=256種狀態(tài),并且他們把這個(gè)8個(gè)開關(guān)稱為一個(gè)字節(jié)一汽。然后這幾個(gè)人美國(guó)人又規(guī)定避消,把編碼(十進(jìn)制值)從0到32的值所表示的狀態(tài)分別規(guī)定了特殊的用途,終端召夹、打印機(jī)等輸出設(shè)備一收到這些值岩喷,就會(huì)做出響應(yīng)的操作。
比如:
輸出設(shè)備遇到0x10监憎,終端就換行均驶。
輸出設(shè)備遇到0x07,終端就發(fā)出嘟嘟的聲響枫虏。(很多人可能不知道命令行其實(shí)是可以發(fā)出警報(bào)聲的)
輸出設(shè)備遇到0x1b妇穴,打印機(jī)就打印反白的字符,或者終端用彩色表示字母隶债。腾它、
輸出設(shè)備遇到...
好了,總之這就是這個(gè)幾個(gè)美國(guó)人設(shè)計(jì)的規(guī)范死讹。這幾個(gè)美國(guó)人感覺這樣子做很不錯(cuò)瞒滴,并且把0x20以下的字節(jié)碼狀態(tài)稱為控制碼
。此時(shí)這幾個(gè)美國(guó)人又把所有的空格赞警、標(biāo)點(diǎn)符號(hào)妓忍、數(shù)字、大小寫字母分別用連續(xù)的字節(jié)狀態(tài)來表示愧旦,一直定義到了127世剖。這樣子當(dāng)著幾個(gè)人每個(gè)人想要輸出字符串到輸出設(shè)備時(shí)候,只要輸入對(duì)應(yīng)的二進(jìn)制的狀態(tài)碼即可笤虫。其實(shí)這也是最早的計(jì)算機(jī)的原型旁瘫。很快,這幾個(gè)美國(guó)人把這個(gè)編碼向全國(guó)推廣琼蚯,希望可以統(tǒng)一字符的標(biāo)準(zhǔn)酬凳。果不其然,這種字符集標(biāo)準(zhǔn)很快在美國(guó)地區(qū)得到普及遭庶,并且有了一個(gè)新的名稱--ASCII(American Standard Code for Information Interchange宁仔,美國(guó)信息互換標(biāo)準(zhǔn)代碼)
編碼。
后來峦睡,隨著計(jì)算機(jī)的普及翎苫。世界各地的人都開始用計(jì)算機(jī)。就以我們國(guó)家做例子赐俗。但是這個(gè)字符集的短板也就出現(xiàn)了拉队,當(dāng)時(shí)中國(guó)人發(fā)現(xiàn)計(jì)算機(jī)無(wú)法輸入中文!原因就在于那幾個(gè)美國(guó)人只定義了英文的英語(yǔ)字符(a,b,c,d...等)阻逮。根本沒有考慮其他國(guó)家的語(yǔ)言輸入粱快。
于是我們勤勞的中國(guó)人打算自己定義一套適用于中文的字符集。我們?cè)?code>ASCII字符集的基礎(chǔ)上叔扼,把127號(hào)的以后的編碼都直接去掉了事哭,規(guī)定:一個(gè)小于127的字符的與原來的意義相同,但是當(dāng)兩個(gè)大于127的字符連在一起瓜富,就表示一個(gè)漢字鳍咱,前面的一個(gè)字節(jié)(稱為高字節(jié))從0xA1用到0xF7,后面一個(gè)字節(jié)(低字節(jié))從0xA1到0xFE与柑,這樣子我們就能夠組合處于大于7000多個(gè)漢子了谤辜。在這些編碼里蓄坏,我們還把數(shù)學(xué)符號(hào)、羅馬希臘的字母丑念、日文的假名們都編進(jìn)去了涡戳,連在 ASCII 里本來就有的數(shù)字、標(biāo)點(diǎn)脯倚、字母都統(tǒng)統(tǒng)重新編了兩個(gè)字節(jié)長(zhǎng)的編碼渔彰,這就是常說的”全角”字符,而原來在127號(hào)以下的那些就叫”半角”字符了推正。中國(guó)人民看到這樣很不錯(cuò)恍涂,于是就把這種漢字方案叫做 “GB2312“。GB2312 是對(duì) ASCII 的中文擴(kuò)展植榕。
欲望推動(dòng)著科技不斷進(jìn)步再沧。后來隨著各種字符文字的增加,又在GB2312的基礎(chǔ)上又?jǐn)U展出了GBK編碼方案内贮、GBK又?jǐn)U展出了GB18030編碼方案产园。
可以看到,ASCII是一個(gè)字節(jié)表示一個(gè)字符夜郁,而為了支持中文漢字的編碼方案什燕,我們出現(xiàn)了兩個(gè)字符表示一個(gè)漢字。但是在計(jì)算機(jī)如何識(shí)別它們呢竞端?答案是計(jì)算機(jī)是按照ASCII的標(biāo)準(zhǔn)進(jìn)行讀取的屎即,所以在當(dāng)時(shí)的編碼方案下,一個(gè)中文是算兩個(gè)英文字符的事富。
在偉大的中國(guó)智慧人民在創(chuàng)新的同時(shí)技俐,在地球上其他國(guó)家其他地區(qū)的人也在創(chuàng)建適用于它們文字的編碼方案。
于是問題就出來了统台。兩個(gè)編碼方案不一樣的計(jì)算機(jī)無(wú)法實(shí)現(xiàn)正常的交流雕擂。
為了解決這個(gè)問題。一個(gè)叫做ISO(國(guó)際標(biāo)準(zhǔn)化組織)
的組織出現(xiàn)了贱勃,它們想解決這個(gè)問題井赌。他們采用的解決方案也很簡(jiǎn)單:廢除了所有的地區(qū)性的編碼方案,自己重新搞了一個(gè)包括了地球上所有文字贵扰、所有字符和符號(hào)的編碼仇穗。然后推廣并讓大家都使用它。這就是Unicode
戚绕。
Unicode
規(guī)定:全部字符都必須用兩個(gè)以及兩個(gè)以上字節(jié)來定義纹坐,也就是必須16位以及16位以上來統(tǒng)一所有的字符,對(duì)于ASCII里的127號(hào)以及以下的字符編碼保持不變舞丛,只是將其長(zhǎng)度從8位擴(kuò)展至16位耘子,高位補(bǔ)0果漾。這樣做的問題也很明顯,就是當(dāng)文本中的所有的字符都是英文時(shí)谷誓,會(huì)浪費(fèi)一倍的儲(chǔ)存空間跨晴。在Unicode
中,無(wú)論這個(gè)文字是由多少個(gè)字節(jié)組成的片林,都是算位一個(gè)字符。
Unicode
存在以下幾個(gè)缺點(diǎn)
- 當(dāng)讀取一個(gè)文本文件時(shí)候的怀骤,計(jì)算機(jī)會(huì)無(wú)法正常讀取文本內(nèi)容费封。
因?yàn)樵趗nicode中的字節(jié)都是可變的,從2個(gè)字節(jié)到N個(gè)字節(jié)不等蒋伦,當(dāng)某個(gè)文本中包含同時(shí)包含有兩個(gè)和三個(gè)字節(jié)的字符時(shí)候弓摘,計(jì)算機(jī)很有可能將三個(gè)字節(jié)的文本拆開來讀取。我這里有一段內(nèi)容為你好簡(jiǎn)書
痕届,假設(shè)這四個(gè)字在Unicode占用的字節(jié)數(shù)分別為:2個(gè)字節(jié)韧献、3個(gè)字節(jié)、3個(gè)字節(jié)研叫。2個(gè)字節(jié)锤窑。那么計(jì)算機(jī)在讀取這段文字的時(shí)候,就很容易讀成22222字節(jié)的方式來讀出五個(gè)字符嚷炉,當(dāng)然渊啰,讀取出來的內(nèi)容和原來的你好簡(jiǎn)書
是完全不一樣的。 - 當(dāng)文本文字中有英文字符的時(shí)申屹,必然會(huì)造成存儲(chǔ)空間的浪費(fèi)绘证。
就像上面我們所說的,unicode中所有的字符都至少為兩個(gè)字節(jié)哗讥,這就出現(xiàn)了問題嚷那。一個(gè)ASCII的英文字只需要占用一個(gè)字節(jié)就可表示,但是在unicode中要用兩個(gè)字節(jié)杆煞。所以當(dāng)文本字符內(nèi)容中英文字符比較多的情況下魏宽,存儲(chǔ)空間、以及網(wǎng)絡(luò)傳輸帶寬(文件傳輸?shù)臅r(shí)候)浪費(fèi)就特別明顯索绪。
由于這些原因湖员,Unicode
在很長(zhǎng)的一段時(shí)間內(nèi)無(wú)法推廣,直到互聯(lián)網(wǎng)的出現(xiàn)瑞驱,為了解決Unicode
如何在網(wǎng)絡(luò)傻姑娘傳輸?shù)膯栴}娘摔,各種各類的UTF(UCS Transfer Format)
的標(biāo)準(zhǔn)誕生了。
UTF
UTF
是個(gè)統(tǒng)稱唤反,它包括了UTF-8
凳寺、UTF-16
等傳輸標(biāo)準(zhǔn)鸭津。不同的地方在于,UTF-8
每次傳輸8個(gè)位的數(shù)據(jù)肠缨,而后者每次傳輸?shù)臄?shù)據(jù)大小為16位逆趋。
UTF-8
目前是互聯(lián)上使用最廣的一種unicode的實(shí)現(xiàn)方式,這是為傳輸而設(shè)計(jì)的編碼晒奕,并使編碼無(wú)國(guó)家闻书。UTF-8
最大的一個(gè)特點(diǎn),就是它是一種變長(zhǎng)的編碼方式脑慧。它可以使用1~4個(gè)字節(jié)來表示一個(gè)字符魄眉,根據(jù)不同的符號(hào)而變化所要存儲(chǔ)的字節(jié)長(zhǎng)度。當(dāng)字符在ASCII碼的范圍內(nèi)闷袒,就用一個(gè)字節(jié)來表示坑律,當(dāng)unicode中一個(gè)漢字表示兩個(gè)字節(jié)時(shí),在UTF-8中用三個(gè)字節(jié)來表示囊骤。
那怎么將unicode轉(zhuǎn)換為utf-8編碼呢晃择?
Unicode符號(hào)范圍 | UTF-8編碼方式
(十六進(jìn)制) | (二進(jìn)制)
----------------------+---------------------------------------------
0 <--> 0x7f | 0xxxxxxx
0x80 <--> 0x7FF | 110xxxxx 10xxxxxx
0x800 <--> 0xFFFF | 1110xxxx 10xxxxxx 10xxxxxx
0x10000 <--> 0x10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
····
····
這就是unicode
轉(zhuǎn)為utf-8的算法規(guī)則。這算法規(guī)則也很好理解也物,以第一行算法為例子宫屠,二進(jìn)制最大的值為01111111
,換算成十六進(jìn)制就是0x7f
焦除。第二行激况、第三行..都以此類推。
這里來舉幾個(gè)例子膘魄。
簡(jiǎn)
字的unicode編碼為\u7b80
,換算成二進(jìn)制為0111 1011 1000 0000
乌逐。從算法規(guī)則中我們可以看到,簡(jiǎn)字在utf-8編碼中创葡,需要三個(gè)字節(jié)表示(0x7b80在0x800與0xFFFF之間)浙踢。然后將簡(jiǎn)字的二進(jìn)制值從低位區(qū)向高位區(qū)填充,高位沒值補(bǔ)0灿渴。那么簡(jiǎn)字在utf-8編碼中的表示為11100111 10101110 10000000
洛波,即0xe7ae80
。
書
字的unicode編碼為\u4e66
,換算成二進(jìn)制為100 1110 0110 0110
骚露。從算法規(guī)則中我們可以得知蹬挤,書字需要三個(gè)字節(jié)來表示,得到結(jié)果為11100100 10111001 10100110
,即0xe4b9a6
棘幸。
A
字的unicode的編碼為\u0041
焰扳,換算成二進(jìn)制為0100 0001
。在utf-8
中,得到的結(jié)果為0100 0001
吨悍。
UTF-8編碼標(biāo)準(zhǔn)很好地解決了我們上面所說的unicode的所面臨的問題扫茅。
utf-8解決了計(jì)算機(jī)無(wú)法正確讀取unicode編碼的問題
如果讀者仔細(xì)觀察utf-8的轉(zhuǎn)換規(guī)則你會(huì)發(fā)現(xiàn)一個(gè)規(guī)律,utf-8編碼的內(nèi)容首個(gè)字節(jié)都是以0育瓜、110葫隙、1110、11110打頭躏仇,后面接上幾個(gè)10開頭的字節(jié)(單字節(jié)時(shí)候不接)恋脚。
這里以讀取某段內(nèi)容為例,計(jì)算機(jī)是這么讀取每個(gè)字符的:
當(dāng)讀取到某個(gè)字節(jié)以0開頭焰手,那么計(jì)算機(jī)就知道這是個(gè)單字節(jié)字符慧起,那么只會(huì)讀取一個(gè)字節(jié)。
當(dāng)讀取到某個(gè)字節(jié)以110開頭的册倒,那么計(jì)算機(jī)除了讀取這個(gè)110開頭的字節(jié)外,還會(huì)讀取緊跟的后面的一個(gè)字節(jié)磺送。
當(dāng)讀取到某個(gè)字節(jié)以1110開頭的驻子,那么計(jì)算機(jī)除了讀取這個(gè)1110開頭的字節(jié)外,還會(huì)讀取緊跟后面的兩個(gè)字節(jié)估灿。
當(dāng)讀取到某個(gè)字節(jié)以11110開頭的崇呵,那么計(jì)算機(jī)除了讀取這個(gè)11110開頭的字節(jié),還會(huì)讀取緊跟后面的三個(gè)字節(jié)馅袁。
....
將該字符所需要的字節(jié)讀取出來后域慷,轉(zhuǎn)換為unicode值(注意這里和上面的轉(zhuǎn)換方式是相反的),最后從unicode值的映射中獲取到這個(gè)值所對(duì)應(yīng)的字符汗销,最后將這個(gè)字符在屏幕上顯示出來犹褒。
在這種編碼下,計(jì)算機(jī)能正確讀取每個(gè)字符的字節(jié)個(gè)數(shù)弛针。從而獲取獲取到正確的字符叠骑。
utf-8解決了存儲(chǔ)空間和網(wǎng)絡(luò)帶寬資源浪費(fèi)的問題
無(wú)論是存儲(chǔ)空間和網(wǎng)絡(luò)帶寬的資源浪費(fèi),都是因?yàn)樵趗nicode中削茁,為英文字符定義了多余的字節(jié)空間造成的宙枷。而utf-8很巧妙地解決了這一問題。從utf-8編碼的轉(zhuǎn)換規(guī)則中我們可以看到茧跋,一個(gè)英文字符在unicode中占用兩個(gè)字節(jié)慰丛,而在utf-8中只占用了一個(gè)字節(jié)。但是其實(shí)瘾杭!在utf-8中漢字的字節(jié)數(shù)反而增加了诅病,比如我們上文說的簡(jiǎn)字,在unicode中占用了兩個(gè)字節(jié),而在utf-8中要用三個(gè)字節(jié)來表示睬隶。不過這并不是資源的浪費(fèi)锣夹,而是utf-8編碼方案為了解決上面所說的那個(gè)問題而規(guī)定的標(biāo)準(zhǔn)。