開(kāi)始記錄我的 2019-Read-Record 記錄一些有意思的知識(shí)點(diǎn)和疑難雜癥舌剂。
1 整理字符工作
有這么一幫人底扳,他們對(duì)字節(jié)顾复,編碼等計(jì)算機(jī)概念一竅不通奏瞬。他們的職責(zé)是對(duì)人類(lèi)日常生活中用到字符和文字進(jìn)行歸納和整理:包含一組不重復(fù),無(wú)序元素的集合泉孩,如下:
此時(shí)集合包含10個(gè)元素硼端,無(wú)序且無(wú)重復(fù)元素,為了更直觀且抽象的表示寓搬,他們采用兩種方式來(lái)表示:
編號(hào) | 字符 |
---|---|
1 | p |
2 | m |
3 | s |
4 | t |
5 | 學(xué) |
6 | 習(xí) |
7 | 字 |
8 | 符 |
9 | 編 |
10 | 碼 |
… ... | … ... |
采用一維數(shù)字編號(hào)珍昨,查找關(guān)系為 1->p
、2->m
以此類(lèi)推.
區(qū)/位 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | p | m | s | t |
2 | 學(xué) | 習(xí) | 字 | 符 |
3 | 編 | 碼 | ... | ... |
4 | ... | ... | ... | ... |
采用二維區(qū)位編號(hào)句喷,查找需要一對(duì)數(shù)字(1,1)->p
镣典、(1,2)->m
以此類(lèi)推。
上述知識(shí)完全不涉及計(jì)算機(jī)知識(shí)唾琼,僅僅是集合中字符元素歸納整理的兩種方式罷了兄春,之后我們談及更多的是一維數(shù)字編號(hào)方式。
2. 計(jì)算機(jī)背景下的字符整理
2.1 定長(zhǎng)編碼
計(jì)算機(jī)底層無(wú)法存儲(chǔ)“p”,“m”...“碼”等字符锡溯,而采用一連串的0赶舆、1表示數(shù)據(jù),以字節(jié)(byte)為單位(00000000 - 11111111趾唱,16進(jìn)制表示為:0x00 - 0xFF)涌乳。因此計(jì)算機(jī)底層真正存儲(chǔ)的是字符編號(hào),注意到計(jì)算機(jī)底層的編號(hào)索引是從0開(kāi)始甜癞,所以我們需要對(duì)方式一編號(hào)稍作修改夕晓。
編號(hào) | 編碼后計(jì)算機(jī)實(shí)際存儲(chǔ) | 字符 |
---|---|---|
1 | 0 | p |
2 | 1 | m |
3 | 2 | s |
4 | 3 | t |
5 | 4 | 學(xué) |
6 | 5 | 習(xí) |
7 | 6 | 字 |
8 | 7 | 符 |
9 | 8 | 編 |
10 | 9 | 碼 |
… ... | ... ... | … ... |
倘若計(jì)算機(jī)用一個(gè)字節(jié)存儲(chǔ)字符的編號(hào),同時(shí)以一個(gè)字節(jié)解讀數(shù)據(jù)悠咱,例如計(jì)算機(jī)底層的一串?dāng)?shù)據(jù)是“00000000 00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000”根據(jù)上表可以解讀為“pmst學(xué)習(xí)字符編碼”蒸辆。
那么問(wèn)題來(lái)了,一個(gè)字節(jié)最多可以表示256個(gè)字符析既。隨著源源不斷地往集合中加入新字符躬贡,編號(hào)不斷累加直到超過(guò)255,一個(gè)字節(jié)已經(jīng)無(wú)法滿足我們的需求眼坏。
因此我們改用兩個(gè)字節(jié)來(lái)表示字符編號(hào)拂玻,同時(shí)以兩個(gè)字節(jié)單位解讀數(shù)據(jù)。此時(shí)范圍為 0x0000 - 0xFFFF宰译,容量為65536個(gè)字符檐蚜。
盡管解決了字符容量問(wèn)題,但是對(duì)于“m”這個(gè)字符我們需要用2個(gè)字節(jié)0x0001來(lái)表示沿侈,原先只需要一個(gè)字節(jié)0x01表示闯第,底層存儲(chǔ)字節(jié)多了一倍。顯然這種并不是我們所期望的缀拭。
2.2 變長(zhǎng)編碼
我們既要考慮集合字符的容量問(wèn)題咳短,又要減少不必要的字節(jié)浪費(fèi)填帽,因此引入了"變長(zhǎng)編碼"概念,對(duì)于那些編號(hào)超過(guò)255的字符咙好,我們保留兩個(gè)字節(jié)表示篡腌,而對(duì)于那些編號(hào)較小的字符,我們當(dāng)然希望使用1個(gè)字節(jié)存儲(chǔ)敷扫,而不是2個(gè)字節(jié)哀蘑。
變長(zhǎng)設(shè)計(jì)的核心問(wèn)題自然就是如何區(qū)分不同的變長(zhǎng)字節(jié),只有這樣才能在解碼時(shí)不發(fā)生歧義葵第。
道理我都懂绘迁,但是按照表格映射關(guān)系解析計(jì)算機(jī)底層數(shù)據(jù)時(shí),何時(shí)用一個(gè)字節(jié)解讀卒密,何時(shí)又用兩個(gè)字節(jié)解讀呢缀台?這是個(gè)棘手的問(wèn)題!
答案是高位區(qū)分哮奇。
編號(hào) | 編碼后計(jì)算機(jī)實(shí)際存儲(chǔ) | 字符 |
---|---|---|
1 | 0 | p |
… | … | … |
65 | 64(0x40) | x |
… | … | … |
128 | 127(0x7F) | h |
129 | 32768(0x80 00) | e |
130 | 32769(0x80 01) | l |
131 | 32770(0x80 02) | o |
… ... | ... ... | … ... |
仔細(xì)觀察上述表格膛腐,集合中字符編號(hào)未變,但是在計(jì)算機(jī)中實(shí)際存儲(chǔ)規(guī)則變了鼎俘,計(jì)算機(jī)現(xiàn)在知道如何區(qū)分用一個(gè)字節(jié)去解讀還是兩個(gè)字節(jié)哲身。當(dāng)計(jì)算機(jī)讀到0x00-0x7F范圍的字節(jié)值,會(huì)直接按照表格映射關(guān)系去解釋?zhuān)缗龅?x40這個(gè)字節(jié)贸伐,會(huì)直接解釋成“x”;而碰到0x80-0xFF范圍字節(jié)值勘天,會(huì)先保留它不作解釋?zhuān)瑫?huì)繼續(xù)讀入下一個(gè)字節(jié)后才進(jìn)行映射,例如“0x80 0x01”捉邢,先讀入0x80脯丝,計(jì)算機(jī)知道處于0x80-0xFF范圍,需要再讀入一個(gè)字節(jié)才能解釋?zhuān)谑窃俅巫x入0x01,合并后為0x8001伏伐,到表格映射關(guān)系中找到對(duì)應(yīng)的字符“l(fā)”宠进。
總結(jié): 這里以一個(gè)簡(jiǎn)單的示例解釋了變長(zhǎng)編碼的概念。上面提到的簡(jiǎn)單變長(zhǎng)編碼方式弊端很多藐翎,首先可表示字符的范圍現(xiàn)在為0x00-0x7F 以及 0x8000 - 0xFFFF材蹬,硬生生地把0x80-0x7FFF這段可利用空間去除了。至于原因吝镣,請(qǐng)思考:倘若這段區(qū)間也做字符映射堤器,那么計(jì)算機(jī)碰到 0x80 或是其他值,它還能區(qū)分用1個(gè)字節(jié)還是2個(gè)字節(jié)解釋嗎赤惊?
3 Unicode 字符集
還記得第一節(jié)說(shuō)到專(zhuān)門(mén)有一幫人負(fù)責(zé)歸納整理世界上所有的字符嗎?即Unicode字符集凰锡,看下定義:
Unicode(統(tǒng)一碼未舟、萬(wàn)國(guó)碼圈暗、單一碼)是計(jì)算機(jī)科學(xué)領(lǐng)域里的一項(xiàng)業(yè)界標(biāo)準(zhǔn),包括字符集、編碼方案等裕膀。Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的员串,它為每種語(yǔ)言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿足跨語(yǔ)言昼扛、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換寸齐、處理的要求。1990年開(kāi)始研發(fā)抄谐,1994年正式公布
現(xiàn)在來(lái)說(shuō)說(shuō)這個(gè)字符集的容量有多少渺鹦。按照之前一維數(shù)字編號(hào),目前范圍是0x0000 - 0x10FFFF(21位 bits)蛹含。這里要引入“平面”這一概念毅厚,一個(gè)平面能放置65536(0xFFFF)個(gè)字符。因此Unicode字符集可以劃分為17個(gè)平面:
平面號(hào) | 平面范圍 |
---|---|
Plane0 —— Basic Multilingual Plane 基本多語(yǔ)言平面 | 0x0000-0xFFFF |
Plane1 —— 后續(xù)16個(gè)平面統(tǒng)稱(chēng)為SP Supplementary Planes) | 0x10000-0x1FFFF |
Plane2 | 0x20000-0x2FFFF |
... | ... |
Plane15 | 0xF0000-0xFFFFF |
Plane16 | 0x100000-0x10FFFF |
用圖表示:
第一個(gè)平面即是BMP(Basic Multilingual Plane 基本多語(yǔ)言平面)浦箱,也叫Plane 0吸耿,它的碼點(diǎn)范圍是U+0000~U+FFFF。這也是我們最常用的平面酷窥,日常用到的字符絕大多數(shù)都落在這個(gè)平面內(nèi)咽安。
上圖彩色的平面即 BMP,范圍為0x0000 - 0xFFFF蓬推,但我們并沒(méi)有“塞滿”這個(gè)平面妆棒,其中部分位置用于保留,換句話說(shuō)拳氢,BMP的容量只有6萬(wàn)多個(gè)字符募逞,想象一下把這些字符整合起來(lái)放在一起,密密麻麻馋评!GNU Unifont就制作了一張這樣的圖片放接。見(jiàn)http://unifoundry.com/pub/unifont-7.0.03/unifont-7.0.03.bmp 打開(kāi)可能會(huì)費(fèi)一點(diǎn)時(shí)間。
前面說(shuō)到BMP是我們最常用的平面留特,日常用到的字符絕大多數(shù)都集中在這里纠脾,而對(duì)于中文來(lái)說(shuō),我們常用[\u4E00-\u9FA5]正則表達(dá)式來(lái)匹配蜕青,其實(shí)也就是指定中文在Unicode字符集中的編號(hào)范圍0x4E00 - 0x9FA5苟蹈。其實(shí)稍加計(jì)算就知道這個(gè)范圍的容量不過(guò)兩萬(wàn)多點(diǎn),中文顯然不止這個(gè)數(shù)右核。其實(shí)9FA5后面還有不少的漢字慧脱,它們中間又還夾雜著一些符號(hào),所以想正確地表示Unicode中的漢字還是個(gè)不小的挑戰(zhàn)贺喝。
這里引用一段:應(yīng)該說(shuō)菱鸥,Unicode處在不斷發(fā)展中宗兼,它有一百多萬(wàn)的空間,目前也只是定義了十萬(wàn)左右的字符氮采,還會(huì)不斷增加殷绍,漢字自然也有可能增加,所以漢字的范圍實(shí)際上是動(dòng)態(tài)的鹊漠,變化的主到。當(dāng)然了,常用的基本落在了這一范圍內(nèi)躯概,而事實(shí)上已經(jīng)包含了許多的不常用漢字登钥,畢竟連只有6千多字的GB2312中都含有大量的不常用漢字。在要求不那么嚴(yán)格的應(yīng)用中楞陷,按以上范圍去判斷基本也OK怔鳖,而“漢字”這一概念實(shí)際上也沒(méi)有準(zhǔn)確定義,比方說(shuō)上圖中一些“偏旁部首”固蛾,這些是“漢字”嗎结执?
平常我們看到諸如 \u
,U+
緊跟十六進(jìn)制數(shù)字艾凯,其實(shí)就是Unicode字符集中字符的編號(hào)了献幔,例如 \u4E00
其實(shí)就是對(duì)應(yīng) Unicode 字符集中的漢字“一”。再次強(qiáng)調(diào)這里其實(shí)不涉及計(jì)算機(jī)知識(shí)趾诗,而是單純的歸納整理的方式而已:一維數(shù)字編號(hào)蜡感。
現(xiàn)在說(shuō)說(shuō)計(jì)算機(jī)底層如何存儲(chǔ)這個(gè)編號(hào),是直接存儲(chǔ)0x4E00嗎恃泪,還是找到一種映射關(guān)系郑兴,將0x4E00處理成其他數(shù)據(jù)后再存儲(chǔ)到計(jì)算底層里,這里涉及的東西很多贝乎,下文會(huì)一一解答情连。
4 UTF - Unicode 轉(zhuǎn)換格式
至此,我們對(duì)Unicode字符集有了初步的了解览效,再次強(qiáng)調(diào)一下:即使對(duì)字節(jié)却舀,編碼等計(jì)算機(jī)概念一竅不通此時(shí)是沒(méi)有任何關(guān)系的!正如前面多次強(qiáng)調(diào)锤灿,字符集就是收集日常所用的所有字符挽拔,然后將其歸納整理,比如Unicode這個(gè)組織用數(shù)字對(duì)每個(gè)字符編號(hào)但校,將世界上所有文字和符號(hào)的字符收錄螃诅,從0開(kāi)始編號(hào),一直到0x10FFFF(強(qiáng)調(diào):21位 bits)。想象下术裸,有一張兩列的表空执,左邊是編號(hào),右邊是對(duì)應(yīng)的字符穗椅,看到這你可能脫口而出:一一對(duì)應(yīng)關(guān)系。即得到編號(hào)0去查表奶栖,就知道對(duì)應(yīng)的字符是NULL
匹表;同理,拿“a”字符去查索引宣鄙,應(yīng)該是0x0041(十進(jìn)制的65)袍镀。
編號(hào) | 字符 |
---|---|
0 | NULL |
... | ... |
0x10FFFF | 未知... |
再換種表示形式,本質(zhì)其實(shí)是一樣的:
更多時(shí)候 Unicode 字符集中的字符表示形式應(yīng)該有兩種:U+[xxxx] 和 \u[xxxx]冻晤,其中x代表十六進(jìn)制數(shù)字苇羡。等等!Uncode字符集范圍不是到21位的 0x10FFFF嗎鼻弧,怎么這里只有4個(gè)x
? 難道是因?yàn)槌S玫亩荚诘谝黄矫鍮MP设江,它的范圍是0x0000-0xFFFF?如果你已經(jīng)開(kāi)始這么思考攘轩,恭喜你叉存,差不多已經(jīng)理解我要表達(dá)的意思了。
4.1 UTF-32
計(jì)算機(jī)底層并非直接存儲(chǔ)"a","1","丁"等字符度帮,而是先轉(zhuǎn)變成另外一種形式后再存儲(chǔ)歼捏。首先想到的且最簡(jiǎn)單的:將編號(hào)以二進(jìn)制存儲(chǔ)。如下:
編號(hào) | 編碼后的索引 | 字符 |
---|---|---|
0 | 0 | NULL |
... | ... | ... |
0x10FFFF | 0x10FFFF | 未知... |
編碼后的索引和之前歸納整理的編號(hào)一模一樣笨篷,編碼或者說(shuō)映射關(guān)系其實(shí)很簡(jiǎn)單:
int map(int idx){
return idx;
}
早期字符數(shù)量還沒(méi)有現(xiàn)在這么龐大瞳秽,使用2個(gè)字節(jié)存儲(chǔ)編號(hào)足矣(0x0000-0xFFFF)。但隨著新字符的加入率翅,字符數(shù)量已經(jīng)超出了上限65536练俐。既然2個(gè)字節(jié)不夠表示,解決方法很簡(jiǎn)單:4個(gè)字節(jié)安聘,即 UTF-32 編碼痰洒。
嘗試下,創(chuàng)建一個(gè)文件并寫(xiě)入內(nèi)容“1a”浴韭,然后以 UTF-32 編號(hào)格式存儲(chǔ)丘喻,接著以16進(jìn)制查看這個(gè)文件內(nèi)容(你可以選擇可視化工具,或者使用hexdump
命令查看)念颈,最后看到的應(yīng)該是0x00000031 0x00000061
泉粉;現(xiàn)在從計(jì)算機(jī)讀取內(nèi)容,即``0x00000031 0x00000061`,編輯器每讀入4個(gè)字節(jié)開(kāi)始解碼嗡靡,查表得到相應(yīng)的字符最后呈現(xiàn)給我們跺撼,這也是最終我們看到的,這里用到的是定長(zhǎng)編碼方式讨彼。
思考:既然 UTF32 存儲(chǔ)方便容易理解歉井,為何不一用到底?干嘛還要UTF8-16哈误,UTF-8呢哩至?
其實(shí)原因前面已經(jīng)提及過(guò)一次:當(dāng)存儲(chǔ)內(nèi)容是英文字符或是其他較小編號(hào)時(shí),每個(gè)字符都要使用4個(gè)字節(jié)來(lái)存儲(chǔ)蜜自,太過(guò)浪費(fèi)菩貌,利用率極低!
為此才有了變長(zhǎng)編碼 UTF-16 和 UTF-8 重荠。
4.2 UTF-8
UTF-8是變長(zhǎng)的編碼方案箭阶,可以有1,2戈鲁,3仇参,4四種字節(jié)組合。在前面的定長(zhǎng)與變長(zhǎng)篇章我們提到UTF-8采用了高位保留方式來(lái)區(qū)別不同變長(zhǎng)婆殿,如下:
0XXXXXXX 有效編碼位:7 bits
110XXXXX 10XXXXXX 有效編碼位:11 bits
1110XXXX 10XXXXXX 10XXXXXX 有效編碼位:16 bits
11110XXX 10XXXXXX 10XXXXXX 10XXXXXX 有效編碼位:21 bits
一問(wèn):為何使用
0
,110
,1110
和11110
來(lái)作為區(qū)分冈敛?使用1
,11
,111
和1111
可以嗎?
一答:顯然使用后者無(wú)法區(qū)分鸣皂,比如讀入第一個(gè)1
抓谴,你無(wú)法確定是一個(gè)字節(jié)數(shù)據(jù);繼續(xù)讀入一個(gè)1
寞缝,你還是無(wú)法確定是2個(gè)字節(jié)數(shù)據(jù)癌压,同理,讀入111
也一樣無(wú)法確定荆陆,但是讀入1111
就可以確定是四個(gè)字節(jié)數(shù)據(jù)了滩届,至于前面的,我們必須讀到0才可以確定幾位數(shù)被啼,比如110
帜消,我們才知道是兩個(gè)字節(jié)數(shù)據(jù),但是這樣使得有效數(shù)據(jù)位變少浓体,因此我們還是打算以0
,110
,1110
和11110
區(qū)分泡挺。
二問(wèn):為何第二位起使用固定的10
?
二答:倘若我們使用0
,10
,110
和1110
區(qū)分讀入幾個(gè)字節(jié)命浴,講道理之后可以不用保留位啊娄猫,直接讀XXXX XXXX
不是利用率更高嗎贱除? —— 我個(gè)人認(rèn)為計(jì)算機(jī)可以從任何一個(gè)字節(jié)讀入,萬(wàn)一從某個(gè)XXXX XXXX
讀取時(shí)媳溺,恰巧這個(gè)數(shù)又是以110
或者1110
打頭的月幌,那一旦開(kāi)始就讀錯(cuò),之后就是連鎖反應(yīng)悬蔽,全部都解釋錯(cuò)誤了扯躺。所以我們對(duì)于后面的字節(jié)也需要保留位,至于10
蝎困,應(yīng)該是為了增加可利用位數(shù)吧缅帘,總不能用11110
來(lái)作為保留位吧。
如上难衰,
0
,10
,110
這些都是保留的固定位,X表示是有效編碼位逗栽。單字節(jié)最高位都是0盖袭,多字節(jié)的最高位都是1.
針對(duì)多字節(jié)來(lái)講,我們可以稱(chēng)之為N(N > 1)字節(jié)模式彼宠,首字節(jié)以“N個(gè)1再加0”打頭鳄虱,后跟“N-1”個(gè)以“10”打頭的字節(jié)。
說(shuō)完用 UTF-8 編碼后的四種計(jì)算機(jī)底層存儲(chǔ)形式凭峡,再來(lái)反向說(shuō)說(shuō)讀取過(guò)程拙已。計(jì)算機(jī)每次讀入一個(gè)字節(jié),以高位來(lái)區(qū)分是用1個(gè)字節(jié)解碼摧冀,還是2倍踪,3,4字節(jié)解碼索昂。
重點(diǎn)來(lái)了:我們實(shí)際真正要用的是有效編碼位建车,也就是X
,UTF-8 編碼就是從這些二進(jìn)制數(shù)據(jù)中提取有效編碼位椒惨,組合得到一個(gè)新的十六進(jìn)制值作為索引去Unicode字符集中查找對(duì)應(yīng)字符缤至。—— 就是這么簡(jiǎn)單康谆!
- 一字節(jié)有效編碼位有7位领斥,2^7=128,可以表示字符集區(qū)間 U+0000U+007F(0127)沃暗。
一字節(jié)留給了ASCII月洛,所以UTF-8兼容ASCII。
- 二字節(jié)有效編碼位只有5+6=11位孽锥,最多只有2^11=2048個(gè)編碼空間膊存,所以數(shù)量眾多的漢字是無(wú)法容身于此的了。字符集區(qū)間 U+0080U+07FF(1282047)使用二字節(jié)。
注意:這里編號(hào)范圍是128~2047隔崎,因?yàn)槿サ袅艘蛔止?jié)的碼點(diǎn)(因?yàn)橄孪奘荱+0080)今艺,所以不會(huì)占滿2048個(gè)編碼空間,是有冗余的爵卒,但你不能把適用于一字節(jié)的碼點(diǎn)放到這里來(lái)編碼虚缎。下同。
這個(gè)需要解釋下為什么范圍設(shè)定了U+0080~U+07FF 其實(shí)可以看下編號(hào)后的字符集 U+D800~U+DFFF 是空的钓株!要用作代理區(qū)实牡,其實(shí)吧就是為了不影響之后的解碼沖突。
- 三字節(jié)模式可看到光是保留位就達(dá)到4+2+2=8位轴合,相當(dāng)一字節(jié)创坞,所以只剩下兩字節(jié)16位有效編碼位,它的容量實(shí)際也只有65536受葛。碼點(diǎn)U+0800U+FFFF(204865535)使用三字節(jié)編碼题涨。
我們前面說(shuō)到,一些漢字字典收錄的漢字達(dá)到了驚人的10萬(wàn)級(jí)別总滩「俣拢基本上,常用的漢字都落在了這三字節(jié)的空間里闰渔,這就是我們常說(shuō)的漢字在UTF-8里用三字節(jié)表示席函。當(dāng)然了,這么說(shuō)并不嚴(yán)謹(jǐn)冈涧,如果這10萬(wàn)的漢字都被收錄進(jìn)來(lái)的話茂附,那些偏門(mén)的漢字自然只能被擠到四字節(jié)空間上去了。
- 四字節(jié)的可以看到它的有效位是3+6+6+6=21位督弓,前面說(shuō)到最大的碼點(diǎn)10FFFF也是21位何之,U+FFFF以上的增補(bǔ)平面的字符都在這里來(lái)表示。
按照UTF-8的模式咽筋,它還可以擴(kuò)展到5字節(jié)溶推,乃至6字節(jié)變長(zhǎng),但Unicode說(shuō)了碼點(diǎn)就到10FFFF奸攻,不擴(kuò)充了蒜危,所以UTF-8最多到四字節(jié)就足夠了。
下面演示如何將漢字”你“(U+4F60)編碼存儲(chǔ)到計(jì)算機(jī)底層睹耐,來(lái)自字符集與編碼一文辐赞,強(qiáng)烈推薦!
上圖顯示了一有效位為 15 位的碼點(diǎn)到三字節(jié)轉(zhuǎn)換的一個(gè)基本原理硝训,我們還可看到原來(lái)4F60 中的一頭一尾的兩個(gè) 4 和 0 在轉(zhuǎn)換后還存在于最終的三字節(jié)結(jié)果中响委。UTF-8 三字節(jié)模式固定了 1110 的開(kāi)頭模式新思,所以多數(shù)漢字總是以 1110 開(kāi)頭鱼填,換成 16 進(jìn)制形式浅碾,1110 就是字母 E。
如果看到一串的 16 進(jìn)制有如下的形式:EX XX XX EX XX XX…每三個(gè)三個(gè)字節(jié)前面都是 E 打頭穴翩,那么它很可能就是一串漢字的 UTF-8 編碼了邀窃。
其它變長(zhǎng)字節(jié)轉(zhuǎn)換道理也類(lèi)似荸哟,其中分組從低位開(kāi)始,高位如不足則補(bǔ)零瞬捕。這里就不再示例了鞍历。
UTF-8 編碼的誤解:首先并非將 Unicode 字符集編號(hào)直接存儲(chǔ)到計(jì)算機(jī)底層,其次前文已經(jīng)說(shuō)過(guò)按照高位來(lái)區(qū)分讀取多少個(gè)字節(jié)(四種情況=1個(gè)字節(jié) - 0xxxxxxx肪虎,2 - 110xxxxx劣砍,3 - 1110xxxx,4 - 11110xxx)扇救,但是對(duì)于讀到的數(shù)據(jù)并非一定是字符在 Unicode 字符集中的編號(hào)刑枝,我們需要通過(guò)提取有效編碼位并組合成新的索引,才去字符集中查詢爵政。
小結(jié):UTF-8 變長(zhǎng)編碼方案很好地幫我們解決了字節(jié)利用率的問(wèn)題,在 UTF32 編碼方案里陶缺,字符"a"底層存儲(chǔ)4個(gè)字節(jié) 0x00000041钾挟。而改用 UTF8 只需要一個(gè)字節(jié)。當(dāng)然嘍饱岸,正如上面演示的 UTF-8編碼方式掺出,比UTF-32編碼f(x){return x}
的要復(fù)雜一些。
另外我們也能注意到苫费,對(duì)于U+0800~U+FFFF范圍內(nèi)(即 BMP 平面)的中文字符汤锨,上文說(shuō)了用 UTF-8 編碼需要三個(gè)字節(jié),但是中文顯然不可能只有6萬(wàn)多個(gè)字百框,那些偏門(mén)的中文在Unicode字符集中的編號(hào)都是在 U+FFFF 之上了闲礼,顯然用 UTF-8 編碼要用四個(gè)字節(jié)。
4.3 UTF-16
UTF-8 作為變長(zhǎng)編碼方案對(duì)于 U+0000~U+07FF 范圍字符的字節(jié)利用率是值得肯定的铐维,而對(duì)于 BMP 中U+0800~U+FFFF 確要用3個(gè)字節(jié)柬泽,至于大于U+FFFF的通通都是4個(gè)字節(jié),有沒(méi)有在這方面更好的方案呢嫁蛇?
有锨并,就是作為壓軸出場(chǎng)的UTF-16,它是一種變長(zhǎng)的2或4字節(jié)編碼模式睬棚。對(duì)于BMP內(nèi)的字符使用2字節(jié)編碼第煮,其它的則使用4字節(jié)組成所謂的代理對(duì)來(lái)編碼解幼。這是如何做到的呢? 且聽(tīng)我一一道來(lái)包警。
還記得GNU Unifont就制作了一張Unicode字符集成員的圖片嗎撵摆?地址:http://unifoundry.com/pub/unifont-7.0.03/unifont-7.0.03.bmp ,請(qǐng)找到D8行至DF行揽趾,這里不對(duì)應(yīng)任何字符台汇,是一塊空白區(qū)域,也就是所謂的代理區(qū)(Surrogate Area)篱瞎,在Unicode字符集中的范圍是U+D800 ~ U+DFFF(容量為2048)苟呐,如果以U+DBFF為中心點(diǎn),將其一分為二俐筋,也就有了高代理區(qū)(D800–DBFF)和低代理區(qū)(DC00–DFFF)兩部分牵素,各占1024。下面請(qǐng)回想第一章的二維表示方式澄者,如果行列都有1024笆呆,那么這張表格就有1024*1024=16*65536= 16 * 0xFFFF
,我們的一個(gè)平面容量為0xFFFF粱挡,所以它恰好可以表示增補(bǔ)的16個(gè)平面中的所有字符赠幕。 這張表格如下:
U+0800~U+DFFF 之所以不對(duì)應(yīng)字符,是因?yàn)橐源矸绞酱鎯?chǔ)到內(nèi)存中時(shí)询筏,只要遇到字節(jié)在這個(gè)范圍內(nèi)的 我們就知道需要進(jìn)行代理轉(zhuǎn)換了榕堰!計(jì)算得到0xFFFF編號(hào)之上的字符了 也就是那16個(gè)補(bǔ)充平面。Unicode 字符集編號(hào)顯然也會(huì)為了編碼適當(dāng)做出調(diào)整嫌套,比如這里為了代理映射逆屡,我們將U+0800~U+DFFF這段區(qū)域保留了,不映射字符了踱讨。
再次強(qiáng)調(diào):UTF-16 是一種變長(zhǎng)的2或4字節(jié)編碼模式魏蔗,我們將 Unicode 字符集劃分2個(gè)范圍來(lái)討論:
字符集區(qū)間 U+0000~U+FFFF:計(jì)算機(jī)以2個(gè)字節(jié)為單位讀取底層數(shù)據(jù),如果讀到的兩個(gè)字節(jié)不在代理區(qū)的范圍(U+D800 ~ U+DFFF)痹筛,那么就直接當(dāng)做編號(hào)直接去Unicode 字符集中查詢莺治,例如0x0041查詢到的是”a“,0x4F60查詢到的是”你“ —— 還記得UTF-8是編碼成3個(gè)字節(jié)存儲(chǔ)和讀取解碼的嗎帚稠?
字符集區(qū)間 U+10000~U+10FFFF产雹,即16個(gè)增補(bǔ)平面:計(jì)算機(jī)同樣以2個(gè)字節(jié)為單位讀取底層數(shù)據(jù),如果讀到的兩個(gè)字節(jié)高代理區(qū)的范圍(D800–DBFF)翁锡,接著再次讀取2個(gè)字節(jié)蔓挖,一定要是低代理區(qū)(DC00–DFFF)。這樣2+2 組成一個(gè)代理對(duì)(Surrogate Pair)查詢對(duì)應(yīng)的字符馆衔。注意必須是這種先高后低的順序瘟判,如果出現(xiàn)兩個(gè)高怨绣,兩個(gè)低,或者先低后高拷获,都是非法的篮撑。
UTF-16的例子其實(shí)很簡(jiǎn)單,核心其實(shí)不過(guò)是上面的那種二維表格匆瓜,(D800赢笨,DC00)對(duì)應(yīng)了 Unicode 字符集中編號(hào)為U+01000的字符;(DBFF驮吱,DFFF)對(duì)應(yīng)了 Unicode 字符集中編號(hào)U+10FFFF的字符茧妒。就是這么簡(jiǎn)單。
5 BOM
reserved