當(dāng)你在處理文本時(shí),如果你不是在寫一些非常古老的代碼(legacy code),那么你一定要使用 Unicode。幸運(yùn)的是,蘋果和 NeXT 一直致力于推動(dòng) Unicode 標(biāo)準(zhǔn)的建立,而 NeXT 在 1994 年推出的 Foundation Kit 則是所有編程語(yǔ)言中最先基于 Unicode 的標(biāo)準(zhǔn)庫(kù)之一。但是,即使 NSString 完全支持 Unicode,還替你干了大部分的重活兒,處理各種語(yǔ)言、各種書寫系統(tǒng)的文本仍然是一個(gè)非常復(fù)雜的事情。作為一個(gè)程序員,有些事情你應(yīng)該知道。
這篇文章里,我會(huì)先向你簡(jiǎn)單地講一下 Unicode 這個(gè)標(biāo)準(zhǔn),然后解釋 NSString 是怎么處理它的,再討論一下你可能會(huì)遇到的一些常見(jiàn)問(wèn)題。
計(jì)算機(jī)沒(méi)法直接處理文本,它只和數(shù)字打交道。為了在計(jì)算機(jī)里用數(shù)字表示文本,我們指定了一個(gè)從字符到數(shù)字的映射。這個(gè)映射就叫做編碼(encoding)。
最有名的一個(gè)字符編碼是 ASCII。ASCII 碼是 7 位的,它將英文字母,數(shù)字 0-9 以及一些標(biāo)點(diǎn)符號(hào)和控制字符映射為 0-127 這些整型。隨后,人們創(chuàng)造了許多不同的 8 位編碼來(lái)處理英語(yǔ)以外的其他語(yǔ)言。它們大多都是基于 ASCII 編碼的,并且使用了 ASCII 沒(méi)有使用的第 8 位來(lái)編入其它字母、符號(hào)甚至是整個(gè)字母表(比如西里爾字母和希臘字母)。
當(dāng)然,這些編碼系統(tǒng)相互之前并不兼容,并且,由于 8 位的空間對(duì)于歐洲的文字來(lái)說(shuō)都不夠,更不用說(shuō)全世界的書寫系統(tǒng)了,因此這種不兼容是肯定會(huì)出現(xiàn)的了。這對(duì)于當(dāng)時(shí)基于文本的操作系統(tǒng)來(lái)說(shuō)是很麻煩的,因?yàn)槟菚r(shí)操作系統(tǒng)只能同時(shí)使用一種編碼(也叫做內(nèi)碼表,code page)。如果你在一臺(tái)機(jī)器上寫了一段文字,然后在另一臺(tái)使用了不同的內(nèi)碼表的機(jī)器上打開(kāi),那么在 128-255 這個(gè)范圍內(nèi)的字符就會(huì)顯示錯(cuò)誤。
諸如中文、日文和韓文的東亞文字又讓情況更加復(fù)雜。這些書寫系統(tǒng)里包含的字符實(shí)在是太多了,以至于 8 位的數(shù)字所能提供的 256 個(gè)位置遠(yuǎn)遠(yuǎn)不夠。結(jié)果呢,人們開(kāi)發(fā)了更加通用的編碼(通常是 16 位的)。當(dāng)你開(kāi)始糾結(jié)于如何處理一個(gè)字節(jié)裝不下的值時(shí),如何把它存儲(chǔ)到內(nèi)存或者硬盤里就變得十分關(guān)鍵了。這時(shí),就必須再進(jìn)行第二次映射,以此來(lái)確定字節(jié)的順序。而且,最好使用可變長(zhǎng)度的編碼而不是固定長(zhǎng)度的。請(qǐng)注意,第二次映射其實(shí)是另一種形式的“編碼”。我們把這兩個(gè)映射都叫做“編碼”很容易造成誤解。這個(gè)在下面 UTF-8 和 UTF-16 的部分里會(huì)再作討論。
現(xiàn)代操作系統(tǒng)都已經(jīng)不再局限于只能同時(shí)使用一種內(nèi)碼表了,因此只要每個(gè)文檔都清楚地標(biāo)明自己使用的是哪種編碼,處理幾十甚至上百種編碼系統(tǒng)盡管很討厭,也完全是有可能的。真正不可能的是在一個(gè)文檔里_混合_使用多種編碼系統(tǒng),因此撰寫多語(yǔ)言的文檔也不可能了,而正是這一點(diǎn)終結(jié)了在 Unicode 編碼出現(xiàn)之前,多種編碼混戰(zhàn)的局面。
1987 年,來(lái)自幾個(gè)大的科技公司(其中包括蘋果和 NeXT)的工程師們開(kāi)始合作致力于開(kāi)發(fā)一種能在全世界所有書寫系統(tǒng)中通用的字符編碼系統(tǒng),于 1991 年 10 月發(fā)布的 1.0.0 版本的 Unicode 標(biāo)準(zhǔn)就是這一努力的成果。
簡(jiǎn)單地來(lái)說(shuō),Unicode 標(biāo)準(zhǔn)為世界上幾乎所有的1書寫系統(tǒng)里所使用的每一個(gè)字符或符號(hào)定義了一個(gè)唯一的數(shù)字。這個(gè)數(shù)字叫做碼點(diǎn)(code points),以 U+xxxx 這樣的格式寫成,格式里的 xxxx 代表四到六個(gè)十六進(jìn)制的數(shù)。比如,U+0041(十進(jìn)制是 65)這個(gè)碼點(diǎn)代表拉丁字母表(和 ASCII 一致)里的字母 A;U+1F61B 代表名為“伸出舌頭的臉”的 emoji,也就是