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