1.html
首先要注意的是,代理Surrogate是專屬於UTF-16編碼方式的一種機制,UTF-8和UTF-32是不用代理的。算法
如前文所述,爲了讓UTF-16能繼續編碼基本平面後面的增補平面中的碼點值,因而擴展了UTF-16編碼方式。編程
具體的擴展方法就是爲其增長了代理機制,用兩個對應於基本平面碼點(即BMP代理區中的碼點)的16位碼元來表示一個增補平面碼點,這兩個用來表示一個增補平面碼點的特殊16位碼元就被稱爲「代理對」。安全
若是要用簡單的一句話來歸納,就是——全部大於0xFFFF的碼點值(即增補平面碼點編號,範圍爲0x10000~0x10FFFF,十進制爲65536~1114111;注意,0xFFFF是十六位二進制數的最大值的十六進制表示)要編碼成UTF-16編碼方式的話,就必須使用代理機制(也就是用代理對來表示)。post
2.性能
在UTF-16編碼方式中,被合起來稱爲」代理對「的這兩個16位碼元就其中的任一單個碼元而言,其實就直接對應於基本平面BMP中的某一個碼點(即BMP中每個碼點的值必然對應於一個16位碼元的值,由於基本平面中的碼點總數爲2^16=65536個,而16位碼元能表示的值也等於2^16=65536個)。測試
這樣一來,就產生了衝突:某個UTF-16碼元究竟是用於表示基本平面字符的碼元,仍是用於表示增補平面字符的代理對中的代理碼元?編碼
所以,爲避免衝突,這些被用做「代理」的任一碼元所對應的碼點在基本平面中均未定義字符,即均沒有指定字符。url
「代理」的真實含義或許就在於此:用兩個基本平面中未定義字符的碼點合起來「代爲署理」增補平面中的碼點。spa
所以,基本平面中這些用做「代理」的碼點區域就被稱之爲「代理區(Surrogate Zone)」,其碼點編號範圍爲0xD800~0xDFFF(十進制55296~57343),共2048個碼點。
3.
增補平面一共有16個平面(即第2平面~第17平面),碼點編號範圍爲0x10000~0x10FFFF(十進制爲65536~1114111,碼點總數爲1048576個)。用兩個代理碼元表示,第一個碼元的取值範圍爲0xD800~0xDBFF(二進制爲1101 1000 0000 0000 ~ 1101 1011 1111 1111,十進制爲55296 ~ 56319),第二個碼元的取值範圍爲0xDC00~0xDFFF(二進制爲1101 1100 0000 0000 ~ 1101 1111 1111 1111,十進制爲56320 ~ 57343)。
所以,增補平面的第一個碼點的編號0x10000其UTF-16編碼就是0xD800 0xDC00(即0x10000經UTF-16編碼後的碼元序列爲0xD800 0xDC00),其他類推。展示爲二進制形式後以下:
====代理碼元1==== ====代理碼元2====
1101 10pp ppxx xxxx 1101 11xx xxxx xxxx
其中代理碼元1中的1101十、代理碼元2中的110111是定數,p、x是變數。去掉定數後組合起來就是pppp xxxx xxxx xxxx xxxx,共20位(2^20=1048576),恰好可以表示增補平面中的所有碼點(0x10000~0x10FFFF,共1048576個)。其中pppp共4位,表示16個增補平面之一的編號(2^4=16);緊接着的16位x表示某個增補平面內的某個碼點(2^16=65536,而65536*16=1048576)。
4.
按照上面的編碼方式,代理對裏面的兩個代理碼元分別稱之爲高16位代理碼元(或稱爲lead surrogates引導代理、前導代理),和低16位代理碼元(或稱爲trail surrogates尾隨代理、後尾代理)。
因爲引導代理和尾隨代理的值分別在0xD800~0xDBFF(十進制爲55296 ~ 56319)之間和0xDC00~0xDFFF(十進制爲56320 ~ 57343)之間,因此首尾兩個代理總共能夠組合出(56319-55296+1)*(57343-56320+1)=1048576個代理對,也就是總共能夠表示1048576個增補碼點,而目前Unicode標準所肯定的16個增補平面的碼點總和也就是65536*16=1048576個。
(笨笨阿林原創文章,轉載請註明出處)
5.
從增補平面的碼點值經過基本平面中的代理對編碼爲增補平面字符的碼元序列的具體算法以下:
1) 增補平面中的碼點值(0x10000~0x10FFFF,二進制爲0001 0000 0000 0000 0000~1 0000 1111 1111 1111 1111,對應的字符名稱爲U+10000~U+10FFFF)減去0x10000(二進制爲0001 0000 0000 0000 0000),可獲得20位長的比特組(值的範圍爲0x00000~0xFFFFF,二進制爲0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111);
2)將獲得的20位長的比特組分拆爲兩部分:高位10比特和低位10比特;
3)20位長的比特組中的高位10比特(值的範圍爲0x000~0x3FF,二進制爲00 0000 0000~11 1111 1111)加上0xD800(二進制爲1101 1000 0000 0000),獲得第一個代理碼元即引導代理(值的範圍是0xD800~0xDBFF,二進制爲1101 1000 0000 0000 ~ 1101 1011 1111 1111);
4)20位長的比特組中的低位10比特(值範圍也是0x000~0x3FF,二進制爲00 0000 0000~11 1111 1111)加上0xDC00(二進制爲1101 1100 0000 0000),獲得第二個代理碼元即尾隨代理(值的範圍是0xDC00~0xDFFF,二進制爲1101 1100 0000 0000 ~ 1101 1111 1111 1111);
5)將引導代理與尾隨代理按先後順序組合在一塊兒成爲「代理對」,就獲得了增補平面字符的碼元序列。
例如,增補平面中碼點值爲10437(字符名稱爲U+10437)的字符(𐐷):
1)0x10437減去0x10000,結果爲0x00437,二進制爲0000 0000 0100 0011 0111。
2)分拆成高10位值和低10位值兩部分:0000000001(即0x0001)及0000110111(即0x0037)。
3)添加0xD800到高位值,以造成高位的引導代理:0xD800 + 0x0001 = 0xD801(二進制爲1101 1000 0000 0001)。
4)添加0xDC00到低位值,以造成低位的尾隨代理:0xDC00 + 0x0037 = 0xDC37(二進制爲1101 1100 0011 0111)。
5)將高位的引導代理與低位的尾隨代理按先後順序組合在一塊兒成爲「代理對」,就獲得了增補平面字符𐐷(字符名稱爲U+10437)的碼元序列:1101 1000 0000 0001 1101 1100 0011 0111。
6.
下表總結了該轉換。不一樣的顏色表示碼點值是如何被分佈到UTF-16碼元序列中的,而由UTF-16編碼過程當中加入的代理附加位則以不一樣的紅色(亮紅色與暗紅色)顯示:
7.
顯然,增補平面中的碼點值從0x10000到0x10FFFF,共計0xFFFFF + 0x1個,即1,048,576個,恰好也就是須要20位來表示(2^20=1,048,576)。若是用兩個16位長的碼元組成的序列來表示,意味着引導代理要容納上述20位中的前10位,尾隨代理要容納上述20位中的後10位。
另外,還要可以根據每一個16位碼元來直接判斷該碼元究竟是屬於引導代理(標誌位爲前6位11 0110,還剩下10位,所以總個數爲2^10=1024個),仍是屬於尾隨代理(標誌位爲前6位11 0111,也剩下10位,所以總個數也是2^10=1024個)。
爲避免衝突,所以須要在基本多語言平面BMP中保留未定義Unicode字符的1024+1024=2048個碼點,就能夠容納引導代理與尾隨代理所須要的編號空間(碼點空間、代碼空間),也就是16個增補平面所須要的編號空間,共計1024*1024=2^20=1048576個碼點。這BMP中的2048個碼點對於BMP總計65536個碼點來講,僅佔3.125%(2048/65536=0.03125)。
8.
在UTF-16編碼方式中,引導代理的後面應該是一個尾隨代理,而尾隨代理的前面就應該是一個引導代理;不能出現一個引導代理的後面是一個非代理的普通UTF-16碼元的狀況,也不能出現一個引導代理的後面仍是一個引導代理的狀況。
UTF-16文本(字符串)的最後一個碼元不能是引導代理,不容許出現一個尾隨代理的前面是一個尾隨代理的狀況,也不容許出現一個尾隨代理的前面是一個非代理的普通UTF-16碼元的狀況;UTF-16文本(字符串)的第一個碼元不能是尾隨代理。
而單獨的一個代理碼元(不論是引導代理仍是尾隨代理)是不合法的,代理必須以一個「引導代理+尾隨代理」編碼對(即代理對)的形式出現。
(笨笨阿林原創文章,轉載請註明出處)
9.
UTF-16的這種「代理對」編碼規則保證了文本處理程序可以正確地訪問和處理包括了基本平面和增補平面在內的所有UTF-16碼元序列,並消除了基本平面字符和增補平面字符之間發生衝突的可能性。
由於引導代理和尾隨代理碼元被各自規定在一個特定範圍內取值,因此很簡單的一個原則就是:凡是在代理編碼範圍內的碼元就是「代理」增補平面SP字符的「代理碼元」,不然就是「基本平面BMP字符的碼元」。因爲BMP中的字符碼元和代理碼元分別在各自獨立的編碼範圍內進行編碼,因此對於一個符合格式規範的UTF-16碼元來說,它必須知足如下三個條件之一:
非代理碼元(BMP字符碼元)必須避開代理碼元所佔用的範圍0xD800~0xDFFF(二進制爲1101 1000 0000 0000 ~ 1101 1111 1111 1111,共2048個);
引導代理必須是代理對中的第一個碼元;
尾隨代理必須是代理對中的第二個碼元。
在處理UTF-16文本時,爲了確保文本數據的完整性,絕對不能把任意一個代理從代理對中拆出來,也不能在代理對中間插入另外一個字符的碼元或碼元序列。
10.
在UTF-16編碼方式裏面,一個Unicode字符碼點值由一個或兩個16位碼元編碼。因此,若是想在一個UTF-16碼元序列裏面判斷某個碼元是屬於哪一個字符的話,就須要檢查那個碼元的值,而後根據碼元的類型(是否具備代理標誌位)決定是否還須要向前或向後檢查一個相鄰的碼元的值(能夠沒必要理會除了先後相鄰的兩個碼元以外的其餘碼元)。
因爲引導代理、尾隨代理、BMP字符碼元,三者互不重疊,搜索就很簡單,這意味着UTF-16具備」自同步」(self-synchronizing)性:經過僅檢查一個碼元就能夠判斷當前字符的下一個字符的起始碼元,每一個字符碼元的邊界很明確;同時,還具備「非傳遞」性:單獨的一個UTF-16碼元出錯涉及的只是一個字符,不會傳遞到文本的其餘部分去,所以,即便文本中某些字符數據遭到破壞,其影響也只是局部性的。
UTF-8也有相似優勢。但許多早期的編碼方式就不是自同步的,好比大多數的多字節編碼標準如GBK、Big5等,必須從頭開始分析文本才能肯定不一樣字符的碼元的邊界;也不具備非傳遞性,局部字符數據被破壞,極可能傳遞到整個文件,致使整個文件沒法正確顯示。
所以,UTF-8和UTF-16編碼方式所具備的「自同步性」、「非傳遞性」等特色除了加強抗干擾能力外,也提供了隨機訪問的能力。
11.
因爲在大多數的文本數據中,代理對(增補字符碼元序列)出現的機率是很小的,不少狀況下處理的仍是非代理碼元(即BMP字符碼元),致使許多軟件處理代理對的部分每每得不到充分的測試。這致使了一些長期的bug與潛在安全漏洞,甚至廣爲流行、獲得良好評價的優秀軟件也是如此。
所以,雖然編程時同時考慮文本中可能出現的不一樣存儲長度的字符(BMP有效字符是單16位編碼,即單碼元編碼;增補字符是雙16位編碼,即雙碼元編碼)並相應作出不一樣的處理,會比單純只考慮16位編碼在性能上要遜色一些。但實際上,現有的遵循定長16位編碼規範但不能處理代理對的程序只需作很小的一點修改就能夠同時處理BMP有效字符和增補字符的編碼了。
另外,須要特別注意的是,雖然Unicode標準規定BMP代理區(U+D800~U+DFFF)的碼點值不對應於任何字符,即未做定義,可是在使用UCS-2的時代,U+D800~U+DFFF是被定義了的,也就是已經用於某些字符了。不過,只要不是剛好構成了代理對,許多程序仍是能把這些不匹配Unicode標準的字符碼元正確地辨識、轉換成合規的碼元。這種由歷史緣由形成的碼元序列按如今的Unicode標準來看,應算做是編碼錯誤。
(笨笨阿林原創文章,轉載請註明出處)
(未完待續)
本系列文章上一篇爲:刨根究底字符編碼之十三——UTF-16編碼方式
【注:個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=w0msvvjoyevv】