Java號(hào)稱對(duì)Unicode提供天然的支持,這話在很久很久以前就已經(jīng)是假的了(不過(guò)曾經(jīng)是真的),實(shí)際上,到JDK5.0為止,Java才算剛剛跟上Unicode的腳步,開(kāi)始提供對(duì)
增補(bǔ)字符的支持。
現(xiàn)在的Unicode碼空間為U+0000到U+10FFFF,一共1114112個(gè)碼位,其中只有1,112,064 個(gè)碼位是合法的(我來(lái)替你做算術(shù),有2048個(gè)碼位不合法),但并不是說(shuō)現(xiàn)在的Unicode就有這么多個(gè)字符了,實(shí)際上其中很多碼位還是空閑的,到Unicode 4.0 規(guī)范為止,只有96,382個(gè)碼位被分配了字符(但無(wú)論如何,仍比很多人認(rèn)為的65536個(gè)字符要多得多了)。其中U+0000 到U+FFFF的部分被稱為
基本多語(yǔ)言面(Basic Multilingual Plane,BMP)。U+10000及以上的字符稱為補(bǔ)充字符。在Java中(Java1.5之后),補(bǔ)充字符使用兩個(gè)char型變量來(lái)表示,這兩個(gè)char型變量就組成了所謂的surrogate pair(在底層實(shí)際上是使用一個(gè)int進(jìn)行表示的)。第一個(gè)char型變量的范圍稱為“高代理部分”(high-surrogates range,從"uD800到"uDBFF,共1024個(gè)碼位), 第二個(gè)char型變量的范圍稱為low-surrogates range(從"uDC00到"uDFFF,共1024個(gè)碼位),這樣使用surrogate pair可以表示的字符數(shù)一共是1024的平方計(jì)1048576個(gè),加上BMP的65536個(gè)碼位,去掉2048個(gè)非法的碼位,正好是1,112,064個(gè)碼位。
關(guān)于Unicode的碼空間實(shí)際上有一些稍不小心就會(huì)讓人犯錯(cuò)的地方。比如我們都知道從U+0000到U+FFFF的部分被稱為基本多語(yǔ)言面(Basic Multilingual Plane,BMP),這個(gè)范圍內(nèi)的字符在使用UTF-16編碼時(shí),只需要一個(gè)char型變量就可以保存。仔細(xì)看看這個(gè)范圍,應(yīng)該有65536這么大,因此你會(huì)說(shuō)單字節(jié)的UTF-16編碼能夠表示65536個(gè)字符,你也會(huì)說(shuō)Unicode的基本多語(yǔ)言面包含65536個(gè)字符,但是再想想剛才說(shuō)過(guò)的surrogate pair,一個(gè)UTF-16表示的增補(bǔ)字符(再一次的,需要兩個(gè)char型變量才能表示的字符)怎樣才能被正確的識(shí)別為增補(bǔ)字符,而不是兩個(gè)普通的字符呢?答案你也知道,就是通過(guò)看它的第一個(gè)char是不是在高代理范圍內(nèi),第二個(gè)char是不是在低代理范圍內(nèi)來(lái)決定,這也意味著,高代理和低代理所占的共2048個(gè)碼位(從0xD800到0xDFFF)是不能分配給其他字符的。
但這是對(duì)UTF-16這種編碼方法而言,而對(duì)Unicode這樣的字符集呢?在Unicode的編號(hào)中,U+D800到U+DFFF是否有字符分配?答案是也沒(méi)有!這是典型的字符集為方便編碼方法而做的安排(你問(wèn)他們這么做的目的?當(dāng)然是希望基本多語(yǔ)言面中的字符和一個(gè)char型的UTF-16編碼的字符能夠一一對(duì)應(yīng),少些麻煩,從中我們也能看出UTF-16與Unicode間很深的淵源與結(jié)合)。也就是說(shuō),無(wú)論Unicode還是UTF-16編碼后的字符,在0x0000至0xFFFF這個(gè)范圍內(nèi),只有63488個(gè)字符。這就好比最初的CPU被勉強(qiáng)拿來(lái)做多媒體應(yīng)用,用得多了,CPU就不得不修正自己從硬件上對(duì)多媒體應(yīng)用提供支持了。
盡管不情愿,但說(shuō)到這里總還得扯扯相關(guān)的概念:代碼點(diǎn)和代碼單元。
代碼點(diǎn)(Code Point)就是指Unicode中為字符分配的編號(hào),一個(gè)字符只占一個(gè)代碼點(diǎn),例如我們說(shuō)到字符“漢”,它的代碼點(diǎn)是U+6C49。
代碼單元(Code Unit)則是針對(duì)編碼方法而言,它指的是編碼方法中對(duì)一個(gè)字符編碼以后所占的最小存儲(chǔ)單元。例如UTF-8中,代碼單元是一個(gè)字節(jié),因?yàn)橐粋€(gè)字符可以被編碼為1個(gè),2個(gè)或者3個(gè)4個(gè)字節(jié);在UTF-16中,代碼單元變成了兩個(gè)字節(jié)(就是一個(gè)char),因?yàn)橐粋€(gè)字符可以被編碼為1個(gè)或2個(gè)char(你找不到比一個(gè)char還小的UTF-16編碼的字符,嘿嘿)。說(shuō)得再羅嗦一點(diǎn),一個(gè)字符,僅僅對(duì)應(yīng)一個(gè)代碼點(diǎn),但卻可能有多個(gè)代碼單元(即可能被編碼為2個(gè)char)。
以上概念絕非學(xué)術(shù)化的繞口令,這意味著當(dāng)你想以一種統(tǒng)一的方式指定自己使用什么字符的時(shí)候,使用代碼點(diǎn)(即你告訴你的程序,你要用Unicode中的第幾個(gè)字符)總是比使用代碼單元更好(因?yàn)檫@樣做的話你還得區(qū)分情況,有時(shí)候提供一個(gè)16進(jìn)制數(shù)字,有時(shí)候要提供兩個(gè))。
例如我們有一個(gè)增補(bǔ)字符???(哈哈,你看到了三個(gè)問(wèn)號(hào)對(duì)吧?因?yàn)槲业南到y(tǒng)顯示不出這個(gè)字符),它在Unicode中的編號(hào)是U+2F81A,當(dāng)在程序中需要使用這個(gè)字符的時(shí)候,就可以這樣來(lái)寫:
String s=String.valueOf(Character.toChars(0x2F81A));
char[]chars=s.toCharArray();
for(char c:chars){
System.out.format("%x",(short)c);
}
后面的for循環(huán)把這個(gè)字符的UTF-16編碼打印了出來(lái),結(jié)果是
d87edc1a
注意到了嗎?這個(gè)字符變成了兩個(gè)char型變量,其中0xd87e就是高代理部分的值,0xdc1a就是低代理的值。