字符編碼演變史

開(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->p2->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

用圖表示:

Unicode 字符集平面

第一個(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ō)上圖中一些“偏旁部首”固蛾,這些是“漢字”嗎结执?

平常我們看到諸如 \uU+ 緊跟十六進(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í)是一樣的:

3.png

更多時(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,111011110來(lái)作為區(qū)分冈敛?使用1,11,1111111可以嗎?
一答:顯然使用后者無(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,111011110區(qū)分泡挺。
二問(wèn):為何第二位起使用固定的 10
二答:倘若我們使用0,10,1101110區(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)單康谆!

  1. 一字節(jié)有效編碼位有7位领斥,2^7=128,可以表示字符集區(qū)間 U+0000U+007F(0127)沃暗。

一字節(jié)留給了ASCII月洛,所以UTF-8兼容ASCII。

  1. 二字節(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í)吧就是為了不影響之后的解碼沖突。

  1. 三字節(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é)空間上去了。

  1. 四字節(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)討論:

  1. 字符集區(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ǔ)和讀取解碼的嗎帚稠?

  2. 字符集區(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末左冬,一起剝皮案震驚了整個(gè)濱河市桐筏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拇砰,老刑警劉巖梅忌,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異除破,居然都是意外死亡牧氮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)瑰枫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)踱葛,“玉大人,你說(shuō)我怎么就攤上這事躁垛∑侍海” “怎么了圾笨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵教馆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我擂达,道長(zhǎng)土铺,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任板鬓,我火速辦了婚禮悲敷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俭令。我一直安慰自己后德,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布抄腔。 她就那樣靜靜地躺著瓢湃,像睡著了一般理张。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绵患,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天雾叭,我揣著相機(jī)與錄音,去河邊找鬼落蝙。 笑死织狐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筏勒。 我是一名探鬼主播移迫,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奏寨!你這毒婦竟也來(lái)了起意?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤病瞳,失蹤者是張志新(化名)和其女友劉穎揽咕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體套菜,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亲善,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逗柴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛹头。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戏溺,靈堂內(nèi)的尸體忽然破棺而出渣蜗,到底是詐尸還是另有隱情,我是刑警寧澤旷祸,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布耕拷,位于F島的核電站,受9級(jí)特大地震影響托享,放射性物質(zhì)發(fā)生泄漏骚烧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一闰围、第九天 我趴在偏房一處隱蔽的房頂上張望赃绊。 院中可真熱鬧,春花似錦羡榴、人聲如沸碧查。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忠售。三九已至者冤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間档痪,已是汗流浹背涉枫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腐螟,地道東北人愿汰。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乐纸,于是被迫代替她去往敵國(guó)和親衬廷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容