And the Lord spake, saying, "First shalt thou take out the Holy Pin. Then, shalt thou count to three. No more. No less. Three shalt be the number thou shalt count, and the number of the counting shall be three. Four shalt thou not count, nor either count thou two, excepting that thou then proceed to three. Five is right out. Once the number three, being the third number, be reached, then, lobbest thou thy Holy Hand Grenade of Antioch towards thy foe, who, being naughty in My sight, shall snuff it."上帝說:『首先取下栓,而後很少很多數到三。應該數到三,你數到的數字是三。你除了數到三,既不要數到四,也不要數到二,五是數多了。「三」一旦被數到,成爲被數到的第三個數字,就高高的向敵人扔出安提拉之神聖手榴彈,阿門。』
—— 巨蟒與聖盃 Monty Python and the Holy Grail (1975)less
UTF-8
的規範裏充斥着這樣神祕的句子:「第一個位元組由110
開始,接着的位元組由10
開始」,「第一個位元組由1110
開始,接着的位元組由10
開始」。spa
那麼這究竟是什麼意思呢?爲何要這麼作呢?設計
咱們先從二進制提及。咱們都知道,一個字節是由8
個二進制位構成的,最小就是0000 0000
,最大就是1111 1111
。那麼一個字節所能表示的最多字符數就是2
的8
次方,也就是256
。對於26
個英文字母來講,大小寫全算上就是52
個,再加上10
個阿拉伯數字,62
個字符,用能夠表達256
個不一樣字符的一個字節來存儲是足夠了。3d
可是,咱們中國的經常使用漢字就有3000
多個,用一個只能表達256
個字符的字節顯然是不夠存儲的。至少也須要有2
個字節,1
個字節是8
個二進制位,2
個字節就是16
個二進制位,最多能夠表達2
的16
次方,也就是256*256=65536
。65536
個字符足夠容納全部中國的漢字,外帶日語、韓語、阿拉伯語、稀其古怪語等等各類各樣的字符。因此這樣就產生了Unicode
,由於它用2
字節表示字符,因此更嚴格來說應該叫UCS-2
,後來由於怪字符太多,2
字節都不夠用了,因此又搞出來了一個4
字節表示的方法,稱做UCS-4
。不過如今對絕大多數人來說UCS-2
已是足夠了。code
Unicode
原本是一個好東西,用2
字節表示65536
種字符,全人類皆大歡喜的事情。可是恰恰有一幫子西洋人,非要認爲這個東西是一種浪費,說咱們英文就最多隻須要26
個字母就夠了,1
個字節就夠了,爲何要浪費2
字節呢?好比說字母A
就是0100 0001
,這一個字節就夠了的東西,你弄2
字節,非要在前面加8
個0
,0000 0000 0100 0001
,這不是浪費嗎?咱們就偏要用1
字節表示英文。blog
好吧,咱們全人類只好作妥協,規定每一個字節,只要看見0
打頭的,就知道這是英文字母,這確定不是漢字,只有看見1
開頭的,才認爲這是漢字。three
可是咱們漢字用1
個字節表示不下,那好辦,用2
個1
開頭的字符表示1
個漢字。這樣原本16
個二進制位,減去2
個開頭的1
,只剩下14
個二進制位了,2
的14
次方就是16384
個字符,對於中文來說,也是足夠用了。可是無奈他們仍是想表達65536
種字符,那怎麼辦呢?就須要3
個字節才能容納得下了,因而UTF-8
粉墨登場。ip
首先,首位爲0
的字符被佔了,只要遇到0
開頭的字符,就知道這是一個1
字節的字符,沒必要再日後數了,直接拿來用就能夠,最多表示128
種字符,從0000 0000
到0111 1111
,也就是從0
到127
。get
接下來的事情就比較蹊蹺了。咱們怎麼用1
開頭的字符既表示2
字節,又表示3
字節呢?假設咱們只判斷首位的1
,這顯然是不行的,沒有辦法區分,因此咱們能夠用10
或者11
開頭的字符來表示2
字節,可是3
字節又該以什麼開頭?或者能夠用10
開頭表示2
字節,用11
開頭表示3
字節?那麼4
字節的字符未來又該怎麼辦?也許咱們能夠用110
開頭表示3
字節,用111
開頭表示4
字節?那麼5
字節6
字節呢?彷佛咱們看到了一個規律:前面的1
越多,表明字節數越多。it
這時候,看一下咱們的第一種方案:用10
開頭表示2
字節,那麼咱們的一個字符將是
10xx xxxx 10xx xxxx
用110
表示3
字節,那麼一個3
字節的字符將是:
110x xxxx 110x xxxx 110x xxxx
這樣無疑是能區分得開的。可是4
字節怎麼辦?
1110 xxxx 1110 xxxx 1110 xxxx 1110 xxxx
嗎?這樣也能區分開,但彷佛有點浪費。由於每一個字節的前半扇都被無用的位佔滿了,真正有意義的只有後面一半。
或者咱們乾脆這樣作得了,咱們來設計方案二:爲了節省起見,全部後面的字符,咱們通通都以10
開頭,只要碰見10
咱們就知道它只是整個字符流的一部分,它確定不是開頭,可是10
這個開頭已經被咱們剛剛方案一的2
字節字符佔用了,怎麼辦?好辦,把2
字節字符的開頭從10
改爲110
,這樣它就確定不會和10
衝突了。因而2
字節字符變成
110x xxxx 10xx xxxx
再日後順推,3
字節字符變成
1110 xxxx 10xx xxxx 10xx xxxx
4
字節字符變成
1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
好像比剛纔的方案一有所節省呢!而且還帶來了額外的好處:若是我沒有見到前面的110
或者1110
開頭的字節,而直接見到了10
開頭的字節,毫無疑問地能夠確定我遇到的不是一個完整字符的開頭,我能夠直接忽略這個錯誤的字節,而直接找下一個正確字符的開頭。
這個改良以後的方案二就是UTF-8
!
如今,咱們來算一下在UTF-8
方案裏,每一種字節能夠表示多少種字符。
1
字節的字符,以0
開頭的,0xxx xxxx
,後面7
個有效位,2
的7
次方,最多能夠表示128
種字符。
2
字節的字符,110x xxxx 10xx xxxx
,數一數,11
個x
,因此是2
的11
次方,2
的10
次方是1024
,11
次方就是2048
,很不幸,只能表示2048
種字符,而咱們的經常使用漢字就有3000
多個,看來在這一區是放不下了,只好挪到3
字節。
3
字節的字符,1110 xxxx 10xx xxxx 10xx xxxx
,數一數,16
個x
,2
的16
次方,最多能夠表示65536
個字符,因此咱們的漢字就放在這一區,因此在UTF-8
方案裏咱們的漢字都是以3
個字節表示的。
因此這也就是這一張表的來歷:
那麼UTF-8
的8
是從哪兒來的呢?它的意思就是說咱們以2
的8
次方爲一個字節,爲一個最小單元。那麼若是咱們以2
的16
次方爲一個最小單元,這就變成了UTF-16
,它的規則和UTF-8
相同,惟一不一樣的是它最小也要用16
個2
進制位表示一個字符,而16
個2
進制位直接能夠表示65536
種字符,因此在UTF-16
方案裏,咱們漢字直接就能夠如英文同樣被冠冕堂皇地放在第1
區了,也就是說,和英文具備同等的身份,都佔用16
個2
進制位,也就至關於UTF-8
裏的2
字節哦,看,這樣一來,若是咱們用UTF-16
來存儲英文的話,會形成浪費,由於英文在UTF-8
裏只佔1
字節,而在UTF-16
裏要佔2
字節,可是若是咱們用UTF-16
來存儲中文的話,不但不浪費,反而還節省了呢!由於咱們的中文在UTF-8
裏要佔用3
字節,而在UTF-16
裏只佔用2
字節,節省了33%
之多呢!