Unicode 與 UTF-8

最近在開發(fā)上遇到了點編碼的問題,又重新學習了字符集以及編碼的知識。特此記錄一下。

核心

我比較喜歡一開始講述核心的東西,這樣子讀者可以帶著這些知識往下看,并且在翻閱到這個頁面的時候可以一眼就可以看到最關(guān)鍵的內(nèi)容。

  • Unicode是字符集
  • UTF-8是編碼規(guī)則

Unicode

在很久很久之前,幾個美國人發(fā)現(xiàn)用8個可以開合的晶體管來表示不同的狀態(tài),即可以表示2^8=256種狀態(tài),并且他們把這個8個開關(guān)稱為一個字節(jié)。然后這幾個人美國人又規(guī)定,把編碼(十進制值)從0到32的值所表示的狀態(tài)分別規(guī)定了特殊的用途,終端、打印機等輸出設(shè)備一收到這些值,就會做出響應(yīng)的操作。
比如:
輸出設(shè)備遇到0x10,終端就換行。
輸出設(shè)備遇到0x07,終端就發(fā)出嘟嘟的聲響。(很多人可能不知道命令行其實是可以發(fā)出警報聲的)
輸出設(shè)備遇到0x1b,打印機就打印反白的字符,或者終端用彩色表示字母。、
輸出設(shè)備遇到...
好了,總之這就是這個幾個美國人設(shè)計的規(guī)范。這幾個美國人感覺這樣子做很不錯,并且把0x20以下的字節(jié)碼狀態(tài)稱為控制碼。此時這幾個美國人又把所有的空格、標點符號、數(shù)字、大小寫字母分別用連續(xù)的字節(jié)狀態(tài)來表示,一直定義到了127。這樣子當著幾個人每個人想要輸出字符串到輸出設(shè)備時候,只要輸入對應(yīng)的二進制的狀態(tài)碼即可。其實這也是最早的計算機的原型。很快,這幾個美國人把這個編碼向全國推廣,希望可以統(tǒng)一字符的標準。果不其然,這種字符集標準很快在美國地區(qū)得到普及,并且有了一個新的名稱--ASCII(American Standard Code for Information Interchange,美國信息互換標準代碼)編碼。

后來,隨著計算機的普及。世界各地的人都開始用計算機。就以我們國家做例子。但是這個字符集的短板也就出現(xiàn)了,當時中國人發(fā)現(xiàn)計算機無法輸入中文!原因就在于那幾個美國人只定義了英文的英語字符(a,b,c,d...等)。根本沒有考慮其他國家的語言輸入。
于是我們勤勞的中國人打算自己定義一套適用于中文的字符集。我們在ASCII字符集的基礎(chǔ)上,把127號的以后的編碼都直接去掉了,規(guī)定:一個小于127的字符的與原來的意義相同,但是當兩個大于127的字符連在一起,就表示一個漢字,前面的一個字節(jié)(稱為高字節(jié))從0xA1用到0xF7,后面一個字節(jié)(低字節(jié))從0xA1到0xFE,這樣子我們就能夠組合處于大于7000多個漢子了。在這些編碼里,我們還把數(shù)學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 里本來就有的數(shù)字、標點、字母都統(tǒng)統(tǒng)重新編了兩個字節(jié)長的編碼,這就是常說的”全角”字符,而原來在127號以下的那些就叫”半角”字符了。中國人民看到這樣很不錯,于是就把這種漢字方案叫做 “GB2312“。GB2312 是對 ASCII 的中文擴展。
欲望推動著科技不斷進步。后來隨著各種字符文字的增加,又在GB2312的基礎(chǔ)上又擴展出了GBK編碼方案、GBK又擴展出了GB18030編碼方案。
可以看到,ASCII是一個字節(jié)表示一個字符,而為了支持中文漢字的編碼方案,我們出現(xiàn)了兩個字符表示一個漢字。但是在計算機如何識別它們呢?答案是計算機是按照ASCII的標準進行讀取的,所以在當時的編碼方案下,一個中文是算兩個英文字符的。

在偉大的中國智慧人民在創(chuàng)新的同時,在地球上其他國家其他地區(qū)的人也在創(chuàng)建適用于它們文字的編碼方案。
于是問題就出來了。兩個編碼方案不一樣的計算機無法實現(xiàn)正常的交流。

為了解決這個問題。一個叫做ISO(國際標準化組織)的組織出現(xiàn)了,它們想解決這個問題。他們采用的解決方案也很簡單:廢除了所有的地區(qū)性的編碼方案,自己重新搞了一個包括了地球上所有文字、所有字符和符號的編碼。然后推廣并讓大家都使用它。這就是Unicode。

Unicode規(guī)定:全部字符都必須用兩個以及兩個以上字節(jié)來定義,也就是必須16位以及16位以上來統(tǒng)一所有的字符,對于ASCII里的127號以及以下的字符編碼保持不變,只是將其長度從8位擴展至16位,高位補0。這樣做的問題也很明顯,就是當文本中的所有的字符都是英文時,會浪費一倍的儲存空間。在Unicode中,無論這個文字是由多少個字節(jié)組成的,都是算位一個字符。

Unicode存在以下幾個缺點

  • 當讀取一個文本文件時候的,計算機會無法正常讀取文本內(nèi)容。
    因為在unicode中的字節(jié)都是可變的,從2個字節(jié)到N個字節(jié)不等,當某個文本中包含同時包含有兩個和三個字節(jié)的字符時候,計算機很有可能將三個字節(jié)的文本拆開來讀取。我這里有一段內(nèi)容為你好簡書,假設(shè)這四個字在Unicode占用的字節(jié)數(shù)分別為:2個字節(jié)、3個字節(jié)、3個字節(jié)。2個字節(jié)。那么計算機在讀取這段文字的時候,就很容易讀成22222字節(jié)的方式來讀出五個字符,當然,讀取出來的內(nèi)容和原來的你好簡書是完全不一樣的。
  • 當文本文字中有英文字符的時,必然會造成存儲空間的浪費。
    就像上面我們所說的,unicode中所有的字符都至少為兩個字節(jié),這就出現(xiàn)了問題。一個ASCII的英文字只需要占用一個字節(jié)就可表示,但是在unicode中要用兩個字節(jié)。所以當文本字符內(nèi)容中英文字符比較多的情況下,存儲空間、以及網(wǎng)絡(luò)傳輸帶寬(文件傳輸?shù)臅r候)浪費就特別明顯。

由于這些原因,Unicode在很長的一段時間內(nèi)無法推廣,直到互聯(lián)網(wǎng)的出現(xiàn),為了解決Unicode如何在網(wǎng)絡(luò)傻姑娘傳輸?shù)膯栴},各種各類的UTF(UCS Transfer Format)的標準誕生了。

UTF

UTF是個統(tǒng)稱,它包括了UTF-8UTF-16等傳輸標準。不同的地方在于,UTF-8每次傳輸8個位的數(shù)據(jù),而后者每次傳輸?shù)臄?shù)據(jù)大小為16位。
UTF-8目前是互聯(lián)上使用最廣的一種unicode的實現(xiàn)方式,這是為傳輸而設(shè)計的編碼,并使編碼無國家。UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節(jié)來表示一個字符,根據(jù)不同的符號而變化所要存儲的字節(jié)長度。當字符在ASCII碼的范圍內(nèi),就用一個字節(jié)來表示,當unicode中一個漢字表示兩個字節(jié)時,在UTF-8中用三個字節(jié)來表示。
那怎么將unicode轉(zhuǎn)換為utf-8編碼呢?


Unicode符號范圍        | UTF-8編碼方式
(十六進制)             | (二進制)
----------------------+---------------------------------------------
      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ī)則也很好理解,以第一行算法為例子,二進制最大的值為01111111,換算成十六進制就是0x7f。第二行、第三行..都以此類推。

這里來舉幾個例子。
字的unicode編碼為\u7b80,換算成二進制為0111 1011 1000 0000。從算法規(guī)則中我們可以看到,簡字在utf-8編碼中,需要三個字節(jié)表示(0x7b80在0x800與0xFFFF之間)。然后將簡字的二進制值從低位區(qū)向高位區(qū)填充,高位沒值補0。那么簡字在utf-8編碼中的表示為11100111 10101110 10000000,即0xe7ae80。

字的unicode編碼為\u4e66,換算成二進制為100 1110 0110 0110。從算法規(guī)則中我們可以得知,書字需要三個字節(jié)來表示,得到結(jié)果為11100100 10111001 10100110,即0xe4b9a6。

A 字的unicode的編碼為\u0041,換算成二進制為0100 0001。在utf-8中,得到的結(jié)果為0100 0001。

UTF-8編碼標準很好地解決了我們上面所說的unicode的所面臨的問題。

utf-8解決了計算機無法正確讀取unicode編碼的問題

如果讀者仔細觀察utf-8的轉(zhuǎn)換規(guī)則你會發(fā)現(xiàn)一個規(guī)律,utf-8編碼的內(nèi)容首個字節(jié)都是以0、110、1110、11110打頭,后面接上幾個10開頭的字節(jié)(單字節(jié)時候不接)。
這里以讀取某段內(nèi)容為例,計算機是這么讀取每個字符的:
當讀取到某個字節(jié)以0開頭,那么計算機就知道這是個單字節(jié)字符,那么只會讀取一個字節(jié)。
當讀取到某個字節(jié)以110開頭的,那么計算機除了讀取這個110開頭的字節(jié)外,還會讀取緊跟的后面的一個字節(jié)。
當讀取到某個字節(jié)以1110開頭的,那么計算機除了讀取這個1110開頭的字節(jié)外,還會讀取緊跟后面的兩個字節(jié)。
當讀取到某個字節(jié)以11110開頭的,那么計算機除了讀取這個11110開頭的字節(jié),還會讀取緊跟后面的三個字節(jié)。
....
將該字符所需要的字節(jié)讀取出來后,轉(zhuǎn)換為unicode值(注意這里和上面的轉(zhuǎn)換方式是相反的),最后從unicode值的映射中獲取到這個值所對應(yīng)的字符,最后將這個字符在屏幕上顯示出來。
在這種編碼下,計算機能正確讀取每個字符的字節(jié)個數(shù)。從而獲取獲取到正確的字符。

utf-8解決了存儲空間和網(wǎng)絡(luò)帶寬資源浪費的問題

無論是存儲空間和網(wǎng)絡(luò)帶寬的資源浪費,都是因為在unicode中,為英文字符定義了多余的字節(jié)空間造成的。而utf-8很巧妙地解決了這一問題。從utf-8編碼的轉(zhuǎn)換規(guī)則中我們可以看到,一個英文字符在unicode中占用兩個字節(jié),而在utf-8中只占用了一個字節(jié)。但是其實!在utf-8中漢字的字節(jié)數(shù)反而增加了,比如我們上文說的簡字,在unicode中占用了兩個字節(jié),而在utf-8中要用三個字節(jié)來表示。不過這并不是資源的浪費,而是utf-8編碼方案為了解決上面所說的那個問題而規(guī)定的標準。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容