轉(zhuǎn)載自ObjeC中國
歷史
計算機沒法直接處理文本,它只和數(shù)字打交道。為了在計算機里用數(shù)字表示文本,我們指定了一個從字符到數(shù)字的映射咨察。這個映射就叫做編碼(encoding)。
ASCII碼是 7 位的福青,它將英文字母扎拣,數(shù)字 0-9 以及一些標點符號和控制字符映射為 0-127 這些整型。隨后,人們創(chuàng)造了許多不同的 8 位編碼來處理英語以外的其他語言二蓝。它們大多都是基于 ASCII 編碼的誉券,并且使用了 ASCII 沒有使用的第 8 位來編入其它字母、符號甚至是整個字母表(比如西里爾字母和希臘字母)刊愚。
由于 8 位的空間對于歐洲的文字來說都不夠踊跟,更不用說全世界的書寫系統(tǒng)了,因此這種不兼容是肯定會出現(xiàn)的了鸥诽。這對于當(dāng)時基于文本的操作系統(tǒng)來說是很麻煩的商玫,因為那時操作系統(tǒng)只能同時使用一種編碼(也叫做內(nèi)碼表)
Unicode 概要
簡單地來說,Unicode 標準為世界上幾乎所有的[^1]書寫系統(tǒng)里所使用的每一個字符或符號定義了一個唯一的數(shù)字牡借。
這個數(shù)字叫做碼點拳昌,以 U+xxxx 這樣的格式寫成,格式里的 xxxx 代表四到六個十六進制的數(shù)钠龙。
原本的unicode被設(shè)計為16位炬藤,提供了 65,536 個字符的空間。后來碴里,考慮到要編碼歷史上的文字以及一些很少使用的日本漢字和中國漢字[^2]沈矿,Unicode 編碼擴展到了 21 位(從 U+0000 到 U+10FFFF),提供了 1,114,112 個碼點咬腋。只有大概 10% 正在使用羹膳,所以還有相當(dāng)大的擴充空間。編碼空間被分成 17 個平面根竿,每個平面有 65,536 個字符陵像。0 號平面叫做基本多文種平面,涵蓋了幾乎所有你能遇到的字符寇壳,除了 emoji蠢壹。其它平面叫做補充平面,大多是空的九巡。
組合字符序列
為了和已有的標準兼容,某些字符可以表示成以下兩種形式之一:一個單一的碼點蹂季,或者兩個以上連續(xù)的碼點組成的序列冕广。例如,有重音符號的字母 é 可以直接表示成 U+00E9(「有尖音符號的小寫拉丁字母 e」)偿洁,或者也可以表示成由 U+0065(「小寫拉丁字母 e」)再加 U+0301(「尖音符號」)組成的分解形式撒汉。這兩個形式都是「組合字符序列」的變體。
在諺文(朝鮮涕滋、韓國的文字)中睬辐,? 這個字可以表示成一個碼點(U+AC00),或者是 ? + ? (U+1100,U+1161)這個序列溯饵。在 Unicode 的語境下侵俗,兩種形式并不相等(因為兩種形式包含不同的碼點),但是符合「標準等價」丰刊,也就是說隘谣,它們有著相同的外觀和意義。
重復(fù)的字符
許多看上去一樣的字符都在不同的碼點編碼了多次啄巧,以此來代表不同的含義寻歧。例如,拉丁字母 A(U+0041)就與西里爾字母 A(U+0410)完全同形秩仆,但事實上码泛,它們是不同的。把它們編入不同的碼點不僅簡化了與老的編碼系統(tǒng)的轉(zhuǎn)換澄耍,而且能讓 Unicode 的文本保留字符的含義噪珊。
但也有極少數(shù)真正的重復(fù),這些完全相同的字符在不同的碼點上定義了多次逾苫。例如卿城,Unicode 聯(lián)盟就列舉出了字母 ?(「上面帶個圓圈的大寫拉丁字母 A」,U+00C5)和字符 ?(「埃米」(長度單位)符號铅搓,U+212B),這兩個字符是完全相同的瑟押。在 Unicode 里,它們也符合標準等價但不相等星掰,在 Unicode 標準里叫做「相容等價」
字形變體
有些字體會為一個字符提供多個「字形變體」多望。Unicode 提供了一個叫做「變體序列」的機制,它允許用戶選擇其中一個變體氢烘。這和組合字符序列的工作機制完全一樣:一個基準字符加上 256 個變體選擇符(VS1-VS256怀偷,U+FE00 到 U+FE0F,還有 U+E0100 到 U+E01EF)中的一個播玖。
Unicode 標準對「標準化變體序列」和「象形文字變體序列」(是由第三方提交給 Unicode 聯(lián)盟的椎工,一旦注冊,它可以被任何人使用)做出了區(qū)分蜀踏。技術(shù)上來講维蒙,兩者并無區(qū)別。
emoji 的樣式就是一個標準化變體序列的例子果覆。許多 emoji 和一些「正陈」的字符都有兩種風(fēng)格:一種是彩色的「emoji 風(fēng)格」,另一種是黑白的局待,更像是符號的「文本風(fēng)格」斑响。例如菱属,「有雨滴的傘」這個字符(U+2614)可能是這樣:?? (U+2614 U+FE0F) ,也可能是這樣的: ?? (U+2614 U+FE0E)舰罚。
Unicode 轉(zhuǎn)換格式
從上文可以看到纽门,字符和碼點之間的映射只完成了一半工作,還需要定義另一種編碼來確定碼點在內(nèi)存和硬盤中要如何表示沸停。Unicode 標準為此定義了幾種映射膜毁,叫做「Unicode 轉(zhuǎn)換格式」(簡稱UTF)
UTF-32
最清楚明了的一個 UTF 就是 UTF-32:它在每個碼點上使用整 32 位。32 大于 21愤钾,因此每一個 UTF-32 值都可以直接表示對應(yīng)的碼點瘟滨。盡管簡單,UTF-32卻幾乎從來不在實際中使用能颁,因為每個字符占用 4 字節(jié)太浪費空間了杂瘸。
UTF-16 以及「代理對」(Surrogate Pairs)的概念
它是根據(jù)有 16 位固定長度的碼元(code units)定義的。UTF-16 本身是一種長度可變的編碼伙菊“苡瘢基本多文種平面(BMP)中的每一個碼點都直接與一個碼元相映射。其它平面里很少使用的碼點都是用兩個 16 位的碼元來編碼的镜硕,這兩個合起來表示一個碼點的碼元就叫做代理對(surrogate pair)运翼。
為了避免用 UTF-16 編碼的字符串里的字節(jié)序列產(chǎn)生歧義,以及能使檢測代理對更容易兴枯,Unicode 標準限制了 U+D800 到 U+DFFF 范圍內(nèi)的碼點用于 UTF-16血淌,這個范圍內(nèi)的碼點值不能分配給任何字符。當(dāng)程序在一個 UTF-16 編碼的字符串里發(fā)現(xiàn)在這個范圍內(nèi)的序列時财剖,就能立刻知道這是某個代理對的一部分悠夯。UTF-16 的這種設(shè)計也是為什么碼點最長也只有奇怪的 21 位的原因。UTF-16 下躺坟,U+10FFFF 是能編碼的最高值沦补。
和所有多字節(jié)長度的編碼系統(tǒng)一樣,UTF-16(以及 UTF-32)還得解決字節(jié)順序的問題咪橙。在內(nèi)存里存儲字符串時夕膀,大多數(shù)實現(xiàn)方式自然都采用自己運行平臺的 CPU 的字節(jié)序(endianness);而在硬盤里存儲或者通過網(wǎng)絡(luò)傳輸字符串時美侦,UTF-16 允許在字符串的開頭插入一個「字節(jié)順序標記」(Byte Order Mask产舞,簡稱 BOM)。字節(jié)順序標記是一個值為 U+FEFF 的碼元音榜,通過檢查文件的頭兩個字節(jié),解碼器就可以識別出其字節(jié)順序捧弃。字節(jié)順序標記不是必須的赠叼,Unicode 標準把高字節(jié)順序(big-endian byte order)定為默認情況擦囊。UTF-16 需要指明字節(jié)順序,這也是為什么 UTF-16 在文件格式和網(wǎng)絡(luò)傳輸方面不受歡迎的一個原因嘴办,不過微軟和蘋果都在自己的操作系統(tǒng)內(nèi)部使用它瞬场。
UTF-8
UTF-8 使用一到四個[^5]字節(jié)來編碼一個碼點。從 0 到 127 的這些碼點直接映射成 1 個字節(jié)(對于只包含這個范圍字符的文本來說涧郊,這一點使得 UTF-8 和 ASCII 完全相同)贯被。接下來的 1,920 個碼點映射成 2 個字節(jié),在 BMP 里所有剩下的碼點需要 3 個字節(jié)妆艘。Unicode 的其他平面里的碼點則需要 4 個字節(jié)彤灶。UTF-8 是基于 8 位的碼元的,因此它并不需要關(guān)心字節(jié)順序(不過仍有一些程序會在 UTF-8 文件里加上多余的 BOM)批旺。
有效率的空間使用(僅就西方語言來講)幌陕,以及不需要操心字節(jié)順序問題使得 UTF-8 成為存儲和交流 Unicode 文本方面的最佳編碼。它也已經(jīng)是文件格式汽煮、網(wǎng)絡(luò)協(xié)議以及 Web API 領(lǐng)域里事實上的標準了搏熄。
NSString 和 Unicode
NSString是完全建立在 Unicode 之上的。但是暇赤,這方面蘋果解釋得并不好心例。這是蘋果的文檔CFString對象的說明(CFString也包含了NSString的底層實現(xiàn)):
從概念上來講,CFString 代表了一個 Unicode 字符組成的數(shù)組和一個字符總數(shù)的計數(shù)鞋囊≈购螅……[Unicode] 標準定義了一個通用、統(tǒng)一的編碼方案失暴,其中每個字符 16 位坯门。
強調(diào)是我(原文作者)加的。這完全是錯誤的逗扒!我們已經(jīng)了解了 Unicode 是一種 21 位的編碼方案古戴。但是有了這樣的文檔吧寺,難怪很多人都認為它是 16 位的呢饮睬。
NSString的文檔同樣誤導(dǎo)人:
一個字符串對象代表著一個 Unicode 字符組成的數(shù)組…… 可以用length方法來獲得一個字符串對象所包含的字符數(shù);用characterAtIndex:方法取得特定的字符奢驯。這兩個簡單的方法為訪問字符串對象提供了基本的途徑黍檩。
這段話初讀起來似乎好一些了叉袍,它沒有又扯淡地講 Unicode 字符是 16 位的。但深究后就會發(fā)現(xiàn)刽酱,characterAtIndex: 這個方法的返回值 unichar 不過是個 16 位的無符號整型罷了喳逛。顯然,它不夠用來表示 21 位的 Unicode 字符:
typedef unsigned short unichar;
事實是這樣的棵里,NSString對象代表的其實是用 UTF-16 編碼的碼元組成的數(shù)組润文。相應(yīng)地姐呐,length方法的返回值也是字符串包含的碼元個數(shù)(而不是字符個數(shù))。
關(guān)于 NSString典蝌,最需要記住的是:NSString 代表的是用 UTF-16 編碼的文本曙砂,長度、索引和范圍都基于 UTF-16 的碼元骏掀。除非你知道字符串的內(nèi)容鸠澈,或者你提前有所防范,不然 NSString 類里的方法都是基于上述概念的截驮,無法給你提供可靠的信息笑陈。每當(dāng)文檔提及「字符」(character)或者 unichar 時,它其實都說的是碼元侧纯。
常見的陷阱
默認情況下新锈,Clang 會把源文件看作以 UTF-8 編碼的。只要你確保 Xcode 以 UTF-8 編碼保存文件眶熬,你就可以直接用字符顯示程序插入任意字符妹笆。如果你更喜歡用碼點,最大到 U+FFFF 這個范圍內(nèi)的碼點你可以以@"\u266A"
(?)的方式輸入娜氏,BMP 外其它平面的碼點則以@"\U0001F340"
(??)的方式輸入拳缠。有意思的是,C99 不允許標準 C 字符集里的字符用通用字符名(universal character name)來指定贸弥,因此不能這樣寫:
NSString *s = @"\u0041"; // Latin capital letter A
// error: character 'A' cannot be specified by a universal character name
我認為應(yīng)該避免使用格式化占位符 %C(使用unichar類型)來創(chuàng)建字符串變量窟坐,因為這樣很容易混淆碼元和碼點。但是在輸出 log 信息時 %C 很有用绵疲。
長度
-[NSString length]
返回字符串里 unichar
的個數(shù)哲鸳。我們已經(jīng)了解了三個可能導(dǎo)致這個返回值與實際(可見)字符數(shù)不符的 Unicode特性。
1.基本多文種平面外的字符:記住盔憨,BMP 里所有的字符在 UTF-16 里都可以用一個碼元表示徙菠。所有其余的字符都需要兩個碼元(一個代理對)∮粞遥基本上所有現(xiàn)代使用的字符都在 BMP 里婿奔,因此在實際中很難遇到代理對。然而问慎,幾年前隨著 emoji 被引入 Unicode(在 1 號平面)萍摊,這種情況已經(jīng)有所變化。emoji 已經(jīng)變得十分普遍如叼,你的代碼必須能夠正確處理它們:
NSString *s = @"\U0001F30D"; // earth globe emoji ??
NSLog(@"The length of %@ is %lu", s, [s length]);
// => The length of ?? is 2
可以用一個小花招解決這個問題冰木,直接計算字符串在 UTF-32 編碼下所需要的字節(jié)數(shù),再除以 4(碼元是16位的笼恰,而一個emoji表情是由兩個碼元組成的踊沸,也就是4個字節(jié)囚衔,因此可以將字符串轉(zhuǎn)化為UTF-32,再除以4個字節(jié)):
NSUInteger realLength = [s lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
NSLog(@"The real length of %@ is %lu", s, realLength);
// => The real length of ?? is 1
2.組合字符序列:如果字母 é 是以分解形式(e + ′)編碼的雕沿,算作兩個碼元:
NSString *s = @"e\u0301"; // e + ′
NSLog(@"The length of %@ is %lu", s, [s length]);
// => The length of é is 2
這個字符串包含了兩個 Unicode 字符,在這個意義上猴仑,返回值 2 是正確的审轮,但顯然正常人都不會這么去數(shù)×伤祝可以用precomposedStringWithCanonicalMapping:
把字符串正規(guī)化成 C 形式(合成形式)來得到更好的結(jié)果:
NSString *n = [s precomposedStringWithCanonicalMapping];
NSLog(@"The length of %@ is %lu", n, [n length]);
// => The length of é is 1
不巧的是疾渣,并不是所有情況都能這樣做,因為只有最常見的組合字符序列有合成形式——其它基礎(chǔ)字符與標記的組合即便是經(jīng)過正規(guī)化后崖飘,也會保持原樣榴捡。如果你想知道字符串真正的字符個數(shù),你只能遍歷字符串自己數(shù)朱浴。后面循環(huán)那一節(jié)會繼續(xù)討論有關(guān)細節(jié)吊圾。
3.變體序列:它們和分解形式的組合字符序列的工作方式一樣,因此變體選擇符也算作單獨的字符翰蠢。
隨機訪問
用characterAtIndex:方法以索引方式直接訪問 unichar會有同樣的問題项乒。字符串可能會包含組合字符序列、代理對或變體序列梁沧。蘋果把這些都叫做合成字符序列(composed character sequence)檀何,這些術(shù)語就變得容易混淆。注意不要把合成字符序列(蘋果的術(shù)語)和組合字符序列(Unicode 術(shù)語)搞混廷支。后者是前者的子集频鉴。可以用rangeOfComposedCharacterSequenceAtIndex:
來確定特定位置的 unichar是不是代表單個字符(可能由多個碼點組成)的碼元序列的一部分恋拍。每當(dāng)給另一個方法傳入一個內(nèi)容未知的字符串的范圍作參數(shù)時都應(yīng)該這樣做垛孔,確保 Unicode 字符不會被從中間分開。
循環(huán)
使用rangeOfComposedCharacterSequenceAtIndex:
的時候芝囤,可以寫一個代碼套路來正確地循環(huán)字符串里所有的字符似炎,但每次要遍歷一個字符串時都得這樣做太不方便了。幸運的是悯姊,NSString
有更好地方式:enumerateSubstringsInRange:options:usingBlock:
方法羡藐。這個方法把Unicode
抽象的地方隱藏了,能讓你輕松地循環(huán)字符串里的組合字符串悯许、單詞仆嗦、行、句子或段落先壕。你甚至可以加上 NSStringEnumerationLocalized
這個選項瘩扼,這樣可以在確定詞語間和句子間的邊界時把用戶所在的區(qū)域考慮進去谆甜。要遍歷單個字符,把參數(shù)指定為 NSStringEnumerationByComposedCharacterSequences:
NSString *s = @"The weather on \U0001F30D is \U0001F31E today.";
// The weather on ?? is ?? today.
NSRange fullRange = NSMakeRange(0, [s length]);
[s enumerateSubstringsInRange:fullRange
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
{
NSLog(@"%@ %@", substring, NSStringFromRange(substringRange));
}];
這個奇妙的方法表明集绰,蘋果想讓我們把字符串看做子字符串的集合规辱,而不是(蘋果意義上的)字符的集合,因為
- 單個
unichar
太小栽燕,不足以代表一個真正的Unicode
字符罕袋; - 一些(普遍意義上的)字符由多個
Unicode
碼點組成。
請注意碍岔,這個方法的加入相對晚一些(在 OS X 10.6 和 iOS 4.0 的時候)浴讯。在之前,按字符循環(huán)一個字符串要麻煩得多蔼啦。
比較
除非你手動執(zhí)行榆纽,否則字符串對象不會自己正規(guī)化。這意味著直接比較包含組合字符序列的字符串可能會得出錯誤的結(jié)果捏肢。isEqual:
和 isEqualToString:
這兩個方法都是一個字節(jié)一個字節(jié)地比較的奈籽。如果希望字符串的合成和分解的形式相吻合,得先自己正規(guī)化:
NSString *s = @"\u00E9"; // é
NSString *t = @"e\u0301"; // e + ′
BOOL isEqual = [s isEqualToString:t];
NSLog(@"%@ is %@ to %@", s, isEqual ? @"equal" : @"not equal", t);
// => é is not equal to é
// Normalizing to form C
NSString *sNorm = [s precomposedStringWithCanonicalMapping];
NSString *tNorm = [t precomposedStringWithCanonicalMapping];
BOOL isEqualNorm = [sNorm isEqualToString:tNorm];
NSLog(@"%@ is %@ to %@", sNorm, isEqualNorm ? @"equal" : @"not equal", tNorm);
// => é is equal to é
另一個選擇是使用compare:
方法(或者它的其它變形方法鸵赫,比如:localizedCompare:
)唠摹,這個方法返回一個和它相容等價的字符串。對此奉瘤,蘋果沒有很好地寫入文檔勾拉。請注意,你常常還需要作標準等價的比較盗温。compare:
沒法作這個比較藕赞。
NSString *s = @"ff";
// ff NSString *t = @"\uFB00";
// ? ligature NSComparisonResult result = [s localizedCompare:t];
NSLog(@"%@ is %@ to %@", s, result == NSOrderedSame ? @"equal" : @"not equal", t);
// => ff is equal to ?
如果你只想用 compare:
比較而不考慮等價關(guān)系,compare:options
這個方法變體可以讓你指定 NSLiteralSearch
作為參數(shù)卖局,這能讓比較更快斧蜕。
從文件或網(wǎng)絡(luò)讀取文本
總地來說,只有當(dāng)你知道文本所用的編碼時文本數(shù)據(jù)才是有用的砚偶。當(dāng)從服務(wù)器下載文本數(shù)據(jù)時批销,通常你都知道或者可以從 HTTP 的頭文件中得知編碼類型。之后染坯,再用 -[NSString initWithData:encoding:]
這個方法創(chuàng)建字符串對象就很簡單了均芽。
雖然文本文件本身并不包含編碼信息,但 NSString常车ヂ梗可以通過查看擴展文件屬性(extended file attributes)或者通過規(guī)律進行試探性的猜測的方法(比如掀宋,一個有效的 UTF-8 文件里就不會出現(xiàn)某些特定的二進制序列)來確定文件的編碼。可以使用-[NSString initWithContentsOfURL:encoding:error:]
這個方法劲妙,來從編碼已知的文件里讀取文本湃鹊。要讀取編碼未知的文件,蘋果提出了以下原則:
如果你不得不猜測文件的編碼(注意镣奋,沒有明確信息币呵,就只有猜測):
- 試試這兩個方法:
stringWithContentsOfFile:usedEncoding:error:
或者initWithContentsOfFile:usedEncoding:error:
(或者這兩個方法參數(shù)為 URL 的等價方法)。這些方法會嘗試猜測資源的編碼侨颈,如果猜測成功富雅,會以引用的形式帶回所用的編碼。- 如果 1 失敗了肛搬,試著用 UTF-8 讀取資源。
- 如果 2 失敗了毕贼,試試合適的老的編碼温赔。
這里「合適的」取決于具體情況。它可以是默認的 C 語言字符串編碼鬼癣,也可以是 ISO 或者 Windows Latin 1 編碼陶贼,亦或者是其它的,取決于你的數(shù)據(jù)來源待秃。- 最終拜秧,還可以試試 Application Kit 里
NSAttributedString
類的載入方法(比如:initWithFileURL:options:documentAttributes:error:
)。這些方法會嘗試純文本文件章郁,然后返回使用的編碼枉氮。可以用這些方法打開任意的文檔暖庄。如果你的程序并不是專業(yè)處理文本的程序聊替,這些方法也值得考慮。對于 Foundation 級別的工具培廓,或者不是自然語言的文本來說惹悄,這些方法可能不太合適。
把文本寫入文件
我已經(jīng)提到過肩钠,純文本文件泣港,和文件格式或者網(wǎng)絡(luò)協(xié)議應(yīng)該選擇 UTF-8 編碼,除非有特別的需要只能用其它的編碼价匠。要向文件中寫入文本当纱,使用 writeToURL:atomically:encoding:error:
這個方法。
這個方法會在 UTF-16 或 UTF-32 編碼的文件上自動加上字節(jié)順序標記踩窖。它還會把文件的編碼存儲在名為 com.apple.TextEncoding的擴展文件屬性里惫东。鑒于initWithContentsOf…: usedEncoding:error:
方法知道有這個屬性,當(dāng)你從文件里載入文本時,使用標準的 NSString
方法就能讓確保使用正確的編碼更加容易廉沮。
擴展閱讀
- Joel Spolsky: 關(guān)于 Unicode 和字符集颓遏,每個程序員絕對、必須要了解的一點內(nèi)容滞时。這篇文章已經(jīng)有 10 年了叁幢,而且不僅限于 Cocoa 編程,但是值得一讀坪稽。
- Ross Carter 在 2012 年 NSCoference 上做了一次名叫「你也可以講 Unicode」 的精彩演講曼玩。演講很有意思,強烈推薦觀看窒百。這篇文章的一部分就是基于 Ross 的演講稿的黍判。NSConference 的 Scotty 人很好,讓 objc.io 的讀者可以觀看這次視頻篙梢。謝了顷帖!
- 維基百科上關(guān)于 Unicode 的文章很棒。
- unicode.org 是 Unicode 聯(lián)盟的官網(wǎng)渤滞,上面不僅有完整的標準和碼表索引贬墩,還有其它很有意思的信息。擴展部分 FAQ 也很棒妄呕。
[^1]:最新的 6.3.0 版本的 Unicode 標準支持 100 種文字和 15 種符號集陶舞,比如數(shù)學(xué)符號和麻將牌。在還沒有提供支持的文字中绪励,有 12 種「仍有人使用的文字」以及 31 種「古老的」或者「已經(jīng)消亡的」文字肿孵。
[^2]:如今,Unicode 編碼了超過 70,000 個統(tǒng)一的中日韓文字(CJK)疏魏,單單這些文字就已經(jīng)遠遠超過了 16 位所提供的空間颁井。
[^3]:就連用其它文字寫成的文檔里也會包含大量這個范圍里的字符。假設(shè)有一個 HTML 文檔蠢护,它的內(nèi)容全部是中文雅宾,但這個文檔的字符里仍將有極大的比例是由 HTML 標記、CSS 樣式葵硕、Javascript 代碼眉抬、空格、換行符等組成的懈凹。
[^4]:我(原文作者蜀变,下同)在 2012 年的一篇博文里質(zhì)疑了讓 UTF-8 兼容 ASCII 的決定是否正確。事實上介评,我現(xiàn)在知道了库北,UTF-8 的核心目標之一就是這個兼容性爬舰,以明確避免與不支持 Unicode 的文件系統(tǒng)之間的問題。不過我還是覺得太多的向前兼容最后往往會成為累贅寒瓦,因為即使在今天情屹,這個特性仍然會把一些漏洞掩蓋在沒有充分測試的處理 Unicode 的代碼里。
[^5]:UTF-8 最初是設(shè)計用來編碼最長達 31 位的碼點的杂腰,而這需要最多達 6 字節(jié)的序列垃你。后來為了遵守 UTF-16 的約束,將它限制到了 21 位∥购埽現(xiàn)在惜颇,最長的 UTF-8 字節(jié)序列是 4 字節(jié)的。