帶你正確認(rèn)識Unicode和UTF-8

UTF-8

前言

相信很多小伙伴跟我一樣,之前很長一段時間對Unicode和UFT-8一直搞得不清不楚,等到用的時候就網(wǎng)上搜一搜,大概懂了點是什么,隔一段時間后又搞忘了,簡直闊怕啊。今天我將帶您輕輕松松出困境,用正確的姿勢學(xué)習(xí)認(rèn)識Unicode和UTF-8

ASCII碼

由于計算機(jī)這玩意兒是英文為母語的老外發(fā)明的,在計算機(jī)發(fā)展早期,能用到計算機(jī)的也就是用英語的那么些國家,因此對于他們來說,用于表示計算機(jī)顯示屏上要顯示的字符也就大小寫的英文字母、數(shù)字、標(biāo)點符號、特殊符號、一些特殊字符再加上些控制字符就足矣,總共加起來一個字節(jié)足矣表示完。因此就出現(xiàn)了ASCII碼。

ASCII碼是從零開始編號,一直到127號,用于表示完上述所有的東西。把從0 ~ 31的32種狀態(tài)用于與打印機(jī)、終端等設(shè)備“約定”為控制碼,就是專門用于“控制”用途的,意思就是只要打印機(jī)啊、終端等設(shè)備啊,遇到這些0 ~ 31的控制碼的時候,就會做他們約定好的動作,例如,當(dāng)遇到0xA的時候,終端就會換行,這樣子工作起來會很好很和諧。因此,0x20之前的這32種狀態(tài)就被用作了 控制碼。

之后從0x20開始依次連續(xù)的對剩余字符進(jìn)行編號,直到127號,這些編號的字符包括空格、標(biāo)點符號、數(shù)字、大小寫字母等。這樣子就能很完美的用不同的字節(jié)表示完上述所有的東西了。這就是ASCII碼。

Unicode

隨著計算機(jī)的發(fā)展和流行,越來越多的國家開始接觸和使用到了計算機(jī),這個時候,問題就隨之而來,英文雖然簡單易學(xué),但是對于非英語國家來說,畢竟也是一門外語,也不是隨隨便便輕輕松松的就可以學(xué)得會的,你不可能讓一個完全不懂英文的中國人很好很熟練的去使用一臺只能顯示英文的計算機(jī)吧,你界面做得再人性化,看不懂上面寫得什么仍然是最痛苦的一件事啊,這樣子也很不利于計算機(jī)的長期發(fā)展。

世界上那么多國家,那么多民族和語言,僅僅一個字節(jié)八位,總共256種狀態(tài),怎么都不能表示得了世界上這么多字符的,我們中國幾千年源遠(yuǎn)流長的文化歷史,光我們近現(xiàn)代才發(fā)展起來的簡體中文,你這一個字節(jié)都是遠(yuǎn)遠(yuǎn)不夠的。

因此后來ISO(International Standardization Organization 國際標(biāo)準(zhǔn)化組織)就重新制定了一套包含地球上所有文化、所有字母和符號的編碼。這就是“Universal Multiple-Octet Coded Character Set”,俗稱Unicode,簡稱UCS,也就是我們常說的通用字符集。

ASCII碼制定的時候還有一個原因,就是當(dāng)年計算機(jī)發(fā)展的早期,存儲空間這些是很小的,對于那時候來說,存儲空間是很奢侈的。記得當(dāng)年比爾蓋茨就說過大概這樣的一句話:幾KB內(nèi)存的計算機(jī)是完全夠用了。然而他萬萬沒想到的是,幾十年后過去了,幾GB甚至成百上千GB的內(nèi)存空間都可能是不夠用的。剛好,在Unicode制定的時候,計算機(jī)的存儲容量得到了極其大的發(fā)展,那時候存儲空間已不再是奢侈的東西了,空間問題從那時開始就已經(jīng)不是什么問題了,于是ISO就用更大的存儲空間來制定新的規(guī)則。

ISO規(guī)定,必須用兩個字節(jié),也就是16位二進(jìn)制來統(tǒng)一的表示所有的字符。這16位二進(jìn)制的數(shù)值就被稱為“code point”,也就是碼點,說白了就是每個字符的編號而已,這個和ASCII碼的編號是一樣的概念,只是換了個名字而已。就比如,碼點0x41就表示大寫字母“A”。

最初的ASCII碼是7位的,后來發(fā)展成了8位,因此ASCII碼的范圍就是0x00 ~ 0xFF。Unicode是16位的,范圍就是0x0000 ~ 0xFFFF,也就是四位十六進(jìn)制表示這一個Unicode字符的,比如“漢”的Unicode 碼點 是 Ox6C49。
這里請注意了,這里用兩個字節(jié)來表示一個Unicode字符,并不是說實際存儲也是用兩個字節(jié)來進(jìn)行存儲一個字符的。并不是這樣的。在存儲的時候,可以用大于兩個字節(jié)的空間去存儲它,就像在32位電腦上用四個字節(jié)去存儲一個整數(shù)1,盡管是用一位就夠了。

什么是UTF-8

從上面提到的,大家也知道了,在Unicode中,對一個字符的表示永遠(yuǎn)都是兩個字節(jié)的,但是實際上,存儲并不都是一直用兩個字節(jié)來進(jìn)行存儲的。這又是為什么呢,為什么表示和存儲會不一樣呢?

Unicode和UTF-8的關(guān)系

通過ISO(國際標(biāo)準(zhǔn)化組織)這個組織的名字也看得出來,它是一個專門制定標(biāo)準(zhǔn)的組織,就例如著名的ISO網(wǎng)絡(luò)七層模型,這網(wǎng)絡(luò)七層模型就是ISO制定出來的網(wǎng)絡(luò)通訊標(biāo)準(zhǔn),但是呢,大家也知道,實際上我們真正網(wǎng)絡(luò)的實現(xiàn)上,并沒有完全套用那七層模型,而是我們熟知的TCP/IP的四層模型。這里就出現(xiàn)了剛提到的實現(xiàn)和制定的標(biāo)準(zhǔn)上的差異。請大家想想問啥會出現(xiàn)這樣的差異問題呢?

最主要的原因其實是這樣的,ISO組織里制定標(biāo)準(zhǔn)的那群人主要就是搞理論知識的,理論這些東西都是很抽象的,就像你去看大學(xué)教材《計算機(jī)網(wǎng)絡(luò)原理》,估計可以看得你欲哭無淚吧,幾乎整本書的理論知識。理論知識都是很抽象的,理解起來難度也偏大,并且跟實際應(yīng)用也是有些許差異的。就像我們的中科院一樣,中科院的院士都是研究理論知識的,而實際上去真正動手干的人是工程院的人,就比如我們親愛的袁隆平爺爺,別人的動手能力直接做出了劃時代意義的雜交水稻,這是實打?qū)嵶龀鰜聿⑶覒?yīng)用于大眾的東西。你讓中科院的去做出來,這是很難的。ISO也是一樣的,他們理論知識搞得很專業(yè),但是呢,缺乏實戰(zhàn)經(jīng)驗,你讓他們把網(wǎng)絡(luò)七層模型直接實現(xiàn)出來,用于大眾,也是不現(xiàn)實的。因此實現(xiàn)網(wǎng)絡(luò)通訊的是那些整天都做生產(chǎn)這些東西的廠商,他們擁有十足的實戰(zhàn)經(jīng)驗,才能真正的做出廣泛用于我們的生活的網(wǎng)絡(luò)通訊協(xié)議。因此就出現(xiàn)了,制定的標(biāo)準(zhǔn)和實際實現(xiàn)上的差異的現(xiàn)象。

同樣的,Unicode也和ISO網(wǎng)絡(luò)七層模型一樣,都是他們制定出來的抽象的理論標(biāo)準(zhǔn),然而真正用于實際生活的實現(xiàn),也是不太現(xiàn)實的。但是標(biāo)準(zhǔn)制定出來也是很有意義的,你制定了統(tǒng)一的標(biāo)準(zhǔn),大家都參照你這個標(biāo)準(zhǔn)去實現(xiàn)去生產(chǎn),那么就能達(dá)到統(tǒng)一性。

因此就出現(xiàn)了UTF,英文是UCS Transfer Format。UTF-8就是專門對Unicode這套理論標(biāo)準(zhǔn)的一種具體實現(xiàn)。UTF-8是參照Unicode標(biāo)注而做出來的真正能用于實際生活的一套東西。說白了就是:Unicode是一套方案,是一個行動的“指導(dǎo)方針”,是理論知識,而UTF-8是這個“指導(dǎo)方針”的具體實現(xiàn),也是其具體”行動“。而Unicode的實現(xiàn)也很多,UTF-8只是其中一種,還包括,UTF-16和UTF-32。重要的東西最后再重復(fù)一遍:UTF-8是Unicode的具體實現(xiàn)之一。

UTF-8具體實現(xiàn)

Unicode雖然能容納上百萬數(shù)量的字符 ,但它只是一個巨大的字符集而已,僅僅規(guī)定了每個符號的二進(jìn)制代碼表示,然鵝并沒有制定具體的存儲規(guī)則,因此它僅限于概念,沒有具體落實到底該怎么去實現(xiàn)。因此到目前為止還只是紙上談兵。這導(dǎo)致 unicode 有不少問題,比如當(dāng)用 3 個字節(jié)存儲一個Unicode字符的時候,它同時也可以被理解為存儲了 3個大小為1字節(jié)的ASCII碼,這是具有二義性的。 另外,我們之前知道ASCII碼只需要一個字節(jié),但是,如果 Unicode規(guī)定每個字符都用 3個字節(jié)來存儲的話,那豈不是活生生浪費(fèi)了兩個字節(jié)的空間?所有這些未經(jīng)細(xì)化的問題都將導(dǎo)致Unicode的不一致性, 因此導(dǎo)致Unicode在很長一段時間內(nèi)無法推廣。

UTF的出現(xiàn),就解決了上述提到的Unicode問題,而UTF-8實現(xiàn)方式又是最通用和常見的一種方式了。因此我們在這里將介紹UTF-8的具體實現(xiàn),讓你真正認(rèn)識到什么是UTF-8.

UTF-8最大的一個特征就是變長存儲的編碼方式。它可以使用1 ~ 4個字節(jié)去存儲不同的字符,根據(jù)不同的字符選擇最合適的字節(jié)長度去進(jìn)行存儲。這樣就起到了合理利用存儲空間的作用。那么UTF-8具體是怎么實現(xiàn)的呢?

UTF-8編碼規(guī)則

UTF-8的編碼規(guī)則很簡單,只有兩條

  • 1、對于單字節(jié)的符號,字節(jié)的第一位設(shè)為 0,后面 7 位為這個符號的 Unicode 碼。因此對于英語宇母, UTF-8編碼和ASCII碼是相同的
  • 2、對于 n 字節(jié)的符號 (n>1),第 一個字節(jié)的前 n 位都設(shè)為 1,第 n+1位設(shè)為 0,后面字節(jié)的前兩位一律設(shè)為 10(注意這里說的是二進(jìn)制10)。剩下的沒有提及的二進(jìn)制位,全部為這個符號的 Unicode碼

可以看看下圖,字母 x 表示可用編碼的位 :


UTF-8編碼規(guī)則表
詳解UTF-8編碼規(guī)則
  • 1、一個字節(jié)的時候
    眾所周知,計算機(jī)長久發(fā)展以來,隨時都做得很好的一個事情就是,新的東西總是會去很好的兼容舊的東西。就例如我們的8086 CPU,現(xiàn)如今,CPU都發(fā)展了好多年好多代了,然而現(xiàn)在的CPU仍然是很好的兼容了幾十年前的8086 CPU。這就是計算機(jī)界一直以來的向下兼容。
    同樣的,字符編碼依然做好了向下兼容的事情,UTF-8也是如此。前面提到過,ASCII碼是使用一個字節(jié)來表示和存儲字符的,用到的是前128個數(shù)值,也就是0 ~ 127。你會發(fā)現(xiàn),UTF-8照樣也會遇到一個字節(jié)的情況,UTF-8遇到一個字節(jié)的時候,為了做到兼容ASCII碼,它的做法就很干脆,就把最高位設(shè)為0,后七位直接使用ASCII碼編碼規(guī)則來進(jìn)行存儲。這樣做完美做到了兼容ASCII碼了。

  • 2、大于一個字節(jié)的時候
    當(dāng)大于一個字節(jié)的時候,由于UTF-8編碼是變長的,因此就出現(xiàn)了一個亟待解決的問題,就是,怎么知道當(dāng)前的一個UTF-8編碼到底占用幾個字節(jié)空間?因此就必須得有一種方式來標(biāo)記當(dāng)前的UTF-8編碼占用了多少個字節(jié)。
    大于一個字節(jié)的時候,我們把第一個字節(jié)稱為高字節(jié),其余的所有字節(jié)都稱之為低字節(jié)。當(dāng)此編碼用幾個字節(jié)存儲,那么它的高字節(jié)的前幾位就為1,然后緊接著的一位設(shè)置為0。其余所有低字節(jié)的高兩位固定為10。為了敘述方便,我們把高字節(jié)和低宇節(jié)中那些用以標(biāo)識 UTF-8 特征的位(不能用于存儲數(shù)據(jù)的位)暫稱為標(biāo)記位。 如果一個 UTF-8 編碼占用了 n個字節(jié),高字節(jié)的高 n位就都是 1,第 n+1 位是 0。低字節(jié)的高位均以 10 開頭 。 除了各字節(jié)高位的標(biāo)記位之外的其他位才是真正存儲數(shù)據(jù)的位,暫稱為數(shù)據(jù)位。

為什么第 n+1位是 0呢?
從上面我們知道,UTF-8的高字節(jié)的決定了當(dāng)前編碼占據(jù)多少字節(jié)。在讀取的時候是怎么確定當(dāng)前編碼占據(jù)多少字節(jié)的呢?就是從高字節(jié)的最高位開始讀取,發(fā)現(xiàn)是當(dāng)前位是1,就加一個字節(jié),那么,怎么知道高字節(jié)中的標(biāo)記位在什么時候結(jié)束呢?我們也知道,數(shù)據(jù)位也是能存儲1的,那么怎么知道在讀取高字節(jié)的位數(shù)數(shù)值的時候,當(dāng)前位是數(shù)據(jù)位還是標(biāo)記位呢?那么,為了解決這個問題,最好的解決方法就是在標(biāo)記位的所有1的后面緊跟著一個0就行了,在讀取數(shù)值的時候,從最高位開始讀取,當(dāng)讀取到第一個0的時候,就認(rèn)定當(dāng)前的標(biāo)記位讀取結(jié)束,這時候讀取到多少個1,就說明當(dāng)前編碼占用多少個字節(jié)。就此完美解決這個問題。

為什么 2 字節(jié)以上(包括 2 字節(jié))的 UTF-8 編碼 ,低字節(jié)的高 2 位始終是固定的 10 呢?

我們上面提到過UTF-8會去兼容 ASCII碼,但是UTF-8的編碼規(guī)則和ASCII不同,必須要特殊處理ASCII碼。 任何編碼在底層上都是二進(jìn)制字節(jié)流 ,解碼器在獲得 1字節(jié)的二進(jìn)制數(shù)據(jù)時,如何知道這是 ASCII 碼,還是 UTF-8編碼的高字節(jié)或低字節(jié)呢?所以 UTF-8 首先要做的是在二進(jìn)制字節(jié)上必須與 ASCII 區(qū)分開來,即在這 1字節(jié)數(shù)據(jù)上做標(biāo)記,通過標(biāo)記就知道這是 UTF-8編碼還是 ASCII碼。ASCII是單字節(jié), 2字節(jié)以上的數(shù)據(jù)用UTF-8編碼才有意義,否則 1個字節(jié)就夠用的話就直接用 ASCII了。ASCII碼范圍是 0 ~ 127, 因此其最高位是0,而2字節(jié)以上的UTF-8編碼其高字節(jié)最高位是1,這樣高字節(jié)己經(jīng)可以和ASCII 區(qū)分了,那么低字節(jié)如何和 ASCII 區(qū)分呢?你可能會說,只要 UTF-8 編碼中低字節(jié)最高位也不為 0 就可以了,即只要是 1 就行 。 其實不然,僅僅最高位為1是區(qū)分不了的。因為如果只要求最高位是 1,那么就有可能和高字節(jié)混淆,比如二進(jìn)制 11001101,這是UTF-8的高字節(jié)還是低字節(jié)?沒有辦法區(qū)分。那么,最不可能成為UTF-8高字節(jié)標(biāo)記位的就是10,我們假設(shè)10是高字節(jié)的標(biāo)記位,按照 UTF-8編碼規(guī)則,說明 UTF-8編碼只用了 1字節(jié), 顯然這是矛盾的,因為在 UTF-8 中 1 字節(jié)的字符用其兼容的 ASCII 碼表示,最高位是0,而不是1,因此UTF-8至少要2字節(jié)以上才有意義,1字節(jié)純粹是為了兼容 ASCII 碼,所以采用 10 作為低字節(jié)的標(biāo)識位才是最合適的。既然標(biāo)識位10只能出現(xiàn)在低字節(jié)的高2位 ,那么反過來說, UTF-8 編碼中以10開頭的都是低字節(jié)。 低字節(jié)有了這個特性便具備了校驗的能力,比如讀取UTF-8編碼的高字節(jié)后,確保后面的低字節(jié)必須以10開頭才是正確的UTF-8編碼。

結(jié)語

我們現(xiàn)在已經(jīng)知道了,Unicode和UTF-8到底是什么了。并且我們也能夠知道,當(dāng)當(dāng)前的UTF-8編碼是兩個字節(jié)的時候,實際上,用于存儲數(shù)據(jù)的位數(shù)是沒有滿滿的兩個字節(jié)(16位),而是11位。為啥呢?因為高字節(jié)的前三位110是標(biāo)志位,低字節(jié)的前兩位10是標(biāo)志位,因此總共的標(biāo)志位就有5位,剩余的存儲為只有11位了。同理,我們也能通過存儲占用的空間大小來反推出UTF-8編碼占據(jù)多少字節(jié)。

希望您對Unicode和UTF-8有了準(zhǔn)確地認(rèn)識了

參考書籍:《自制編程語言-基于C語言》 -- 鄭鋼著

最后編輯于
?著作權(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)容