前幾天看文初的《精武門之Web安全研討會首日感受》,,說到利用字符集攻擊時提到以前寶寶寫的一篇有關(guān)國際化的文章,趁機(jī)再次拜讀了寶寶的這篇大作,,不得不感慨寶寶的寫作功底,無敵,!這么好的文章不分享出來實在是太可惜了,,在此將寶寶的大作轉(zhuǎn)帖于此; 作者序在我開發(fā)Java程序的幾年中,,遇到得最多,,也是別人向我提問最多的問題,就是各種各樣看似稀奇古怪的中文亂碼問題了,。網(wǎng)上也有許多解釋和解決Java中文問題的文章,,但水平參差不齊,有一些文章甚至是錯誤的。 此外,,我們公司自己的Java程序從一開始就采用了錯誤的方式處理中文問題,,雖能解一時之急,卻引出了越來越多的深遠(yuǎn)的問題,。每當(dāng)我聽到有的同事還在討論如何特殊處理雙字節(jié)的中文GB碼,,就感慨他們思路的狹隘。試問,,今天我們可以用特殊的方式處理我們所熟悉的中文編碼,,可是今后我們怎樣才能應(yīng)付日文版、韓文版,、或世界其它國家語言的產(chǎn)品開發(fā)呢,? 在我看來,與其說這些問題是“中文化問題”,,不如說是“國際化問題”,。所謂的“漢化”這種說法已經(jīng)隨時代遠(yuǎn)去了。想想看,,這個詞帶有明顯的小農(nóng)經(jīng)濟(jì)的色彩:自家漢化自家用,,哪管世界變化多。經(jīng)過漢化的軟件,,常常意味著:版本落后,、不兼容、不穩(wěn)定,。為什么會這樣呢,?根本原因是,從軟件的設(shè)計階段,,就沒有考慮國際用戶的需要,,沒有采用國際通用的標(biāo)準(zhǔn)。事后要彌補(bǔ)自然難上加難,。 所以讓我們把眼光放開,,想一想“國際化”。當(dāng)然國際化的目的還是生產(chǎn)出“漢化”的軟件,,但我們可以用同樣的方法“韓化”,、“日化”、“阿拉伯化”,,統(tǒng)稱為“本地化” —— 這就是“國際化”的目的,。國際化和本地化有兩個很體面的英文縮寫:I18n(Internationalization)和L10n(Localization)。 想要開發(fā)出國際化的軟件產(chǎn)品,,首先要了解國際標(biāo)準(zhǔn),,而不是使用東拼西湊的權(quán)宜之計,。本文首先從相關(guān)國際標(biāo)準(zhǔn)的討論切入,相信正確地理解和應(yīng)用這些標(biāo)準(zhǔn),,所有的“中文化問題”或“國際化問題”都會迎刃而解,。 字符編碼簡介ASCII碼從學(xué)計算機(jī)的那天開始,老師就告訴我們在計算機(jī)里面,,所有的英文字母都對應(yīng)到一個數(shù)字編碼,,這就是ASCII碼(American Standard Code for Information Interchange)。ASCII碼是很久很久以前(1968年)制定的,。它只使用了一個8位字節(jié)中的低7位,,總共是127個編碼位。這樣的方案很快就不夠使用了,。
單字節(jié)編碼的發(fā)展在80年代早期,,一些現(xiàn)在流行的標(biāo)準(zhǔn)(如ISO 8859和Unicode)還未出現(xiàn),。那時為了支持多種地區(qū)的語言,各大組織機(jī)構(gòu)或IT廠商開始發(fā)明它們自己的編碼方案,,以便彌補(bǔ)ASCII編碼的不足。一時間,,各種互不相容的字符編碼方案成百花齊放之勢,。 為了避免混亂,,ISO組織在1998年之后,,陸續(xù)發(fā)表了一系列代號為8859的標(biāo)準(zhǔn),,作為ASCII編碼的標(biāo)準(zhǔn)擴(kuò)展,終于統(tǒng)一了單字節(jié)的西方字符的編碼,。ISO是設(shè)在瑞士的國際標(biāo)準(zhǔn)化組織的簡稱(International Organization for Standardization),。 ISO-8859-1(Latin1 - 西歐字符) ISO-8859-1覆蓋了大多數(shù)西歐語言,包括:法國,、西班牙,、葡萄牙,、意大利、荷蘭,、德國,、丹麥、瑞典,、挪威,、芬蘭、冰島,、愛爾蘭,、蘇格蘭、英格蘭等,,因而也涉及到了整個美洲大陸,、澳大利亞和非洲很多國家的語言。 此外,,ISO-8859-1后來被采納為ISO-10646標(biāo)準(zhǔn)(后面會講到)的首頁,,換句話說,Unicode的最開頭256個字符編碼和ISO-8859-1是一一對應(yīng)的,。正是由于這個特殊性,,使很多人產(chǎn)生了對ISO-8859-1編碼的誤用。
ISO-8859標(biāo)準(zhǔn)還包括:
但是ISO 8859系列標(biāo)準(zhǔn)的字符編碼,,還是互不相容,不可能同時使用的,。畢竟它們只是單字節(jié)的編碼方案,。而且,它們和多字節(jié)的編碼方案如中文編碼GB2312和BIG5也是不相容的,。那些歐洲字符(最高位為1的字符),,在GB2312和BIG5中被認(rèn)為是雙字節(jié)漢字編碼的首字節(jié),。 多字節(jié)編碼的發(fā)展單字節(jié)編碼只有256個碼位(28=256),而中文字符何止千千萬,,單字節(jié)編碼不可能滿足中文編碼的需要,。于是為了適應(yīng)東方文字信息處理的需要,ISO又制定了ISO 2022標(biāo)準(zhǔn)(Character code structure and extension techniques),,提供了七位與八位編碼字符集的擴(kuò)充方法的標(biāo)準(zhǔn),。我國根據(jù)ISO 2022制定了國家標(biāo)準(zhǔn)GB2311 ——《信息交換用七位編碼字符集的擴(kuò)充方法》,并根據(jù)該標(biāo)準(zhǔn)制定了國家標(biāo)準(zhǔn)GB2312-80編碼,。其他東方國家和地區(qū)也制定了各自的字符編碼標(biāo)準(zhǔn),,如日本的JIS0208,韓國的KSC5601,,臺灣地區(qū)的CNS11643等,。 BIG5 BIG5是從CNS11643的早期版本發(fā)展而來的,雖然沒有包括CNS11643的全部內(nèi)容,,但卻是目前臺灣,、香港地區(qū)普遍使用的一種繁體漢字的市場標(biāo)準(zhǔn),包括440個符號,,一級漢字5401個,、二級漢字7652個,共計13060個漢字,。 GB2312-80 全稱是《信息交換用漢字編碼字符集 基本集》,,1980年發(fā)布,是中文信息處理的國家標(biāo)準(zhǔn),,在大陸及海外使用簡體中文的地區(qū)(如新加坡等)是強(qiáng)制使用的唯一中文編碼,。 · 雙字節(jié)編碼 · A1-A9:符號區(qū),包含682個符號 · B0-F7:漢字區(qū),,包含6763個漢字 GB2312碼共收錄6763個簡體漢字,、682個符號,其中漢字部分:一級字3755,,以拼音排序,,二級字3008,以偏旁排序,。該標(biāo)準(zhǔn)的制定和應(yīng)用為規(guī)范,、推動中文信息化進(jìn)程起了很大作用。 GBK 漢字內(nèi)碼擴(kuò)展規(guī)范(GBK)是國家技術(shù)監(jiān)督局1995年為中文Windows 95所制定的新的漢字內(nèi)碼規(guī)范,。 · 雙字節(jié)編碼,,GB2312-80的擴(kuò)充,在碼位上和GB2312-80兼容,。 · 范圍:8140 ~ FEFE(剔除xx7F)共23940個碼位,。 · 包含21003個漢字,包含了ISO 10646中的全部中日韓漢字,,簡,、繁體字融于一庫。 嚴(yán)格說,,GBK不能算是國家標(biāo)準(zhǔn),,最多算是一個商業(yè)標(biāo)準(zhǔn)。而GB18030才是真正的國家標(biāo)準(zhǔn),。 GB18030-2000 全稱是《信息交換用漢字編碼字符集》,,是我國的強(qiáng)制標(biāo)準(zhǔn),所有不支持GB18030標(biāo)準(zhǔn)的軟件將不能作為產(chǎn)品出售,。 · 單字節(jié),、雙字節(jié)、四字節(jié)編碼,。 · 向下與GB2312編碼兼容,。 · 支持GB 13000.1-1993中的全部中、日,、韓(CJK)統(tǒng)一漢字字符和全部CJK統(tǒng)一漢字?jǐn)U展A的字符,。 雖然GB18030標(biāo)準(zhǔn)非常強(qiáng)大,但它是一個中國大陸的標(biāo)準(zhǔn),。在編碼上,,除了和GB2312以外,還是不能和世界上其它任何一種字符編碼統(tǒng)一,。 終極標(biāo)準(zhǔn) —— Unicode和ISO 10646前面所講的一切字符編碼方案,,都是針對局部地區(qū)或少數(shù)語言文字的,沒有辦法同時表達(dá)所有的語言文字,,或在多種語言平臺上交換,。這對今天極其頻繁的國際信息交流是不相稱的。 為了提高計算機(jī)的信息處理和交換功能,,使得世界各國的文字都能在計算機(jī)中處理,,從1984年起,ISO組織就開始研究制定一個全新的標(biāo)準(zhǔn):通用多八位編碼字符集(Universal Multiple-Octet Coded Character Set),,簡稱UCS,。標(biāo)準(zhǔn)的編號為:ISO 10646。這一標(biāo)準(zhǔn)為世界各種主要語言的字符(包括簡體及繁體的中文字)及附加符號,,編制統(tǒng)一的內(nèi)碼,。 統(tǒng)一碼(Unicode)是Universal Code的縮寫,是由另一個叫“Unicode學(xué)術(shù)學(xué)會”(The Unicode Consortium)的機(jī)構(gòu)制定的字符編碼系統(tǒng)。Unicode與ISO 10646國際編碼標(biāo)準(zhǔn)從內(nèi)容上來說是同步一致的,。 Unicode是Java語言和XML的基礎(chǔ),,所以我們要稍微詳細(xì)地介紹一下Unicode以及ISO 10646標(biāo)準(zhǔn)。 注意:不夠耐心的讀者可以跳過本章的余下部分,。但顯然了解本章所描述的Unicode及相關(guān)編碼的技術(shù)細(xì)節(jié),,有利于你更好地理解和應(yīng)用Unicode。 Unicode和ISO 10646的關(guān)系在1991年,,Unicode學(xué)術(shù)學(xué)會與ISO國際標(biāo)準(zhǔn)化組織決定共同制訂一套適用于多種語言文本的通用編碼標(biāo)準(zhǔn),。Unicode與ISO 10646國際編碼標(biāo)準(zhǔn)于1992年1月正式合作發(fā)展一套通用編碼標(biāo)準(zhǔn)。自此,,兩個組織便一直緊密合作,,同步發(fā)展Unicode及ISO 10646國際編碼標(biāo)準(zhǔn)。
雖然兩個組織保持如此密切的合作關(guān)系,,但Unicode和ISO 10646還是有區(qū)別的,。ISO 10646著重定義字符編碼,,而Unicode則在此基礎(chǔ)上,為這些字符及編碼數(shù)據(jù)提出應(yīng)用的方法以及對語義數(shù)據(jù)作補(bǔ)充,。 UCS的結(jié)構(gòu)UCS的結(jié)構(gòu)是一個四維的編碼空間,,每一維由一個字節(jié)(八位二進(jìn)制位)組成,范圍是00到FF,??傮w上分為128個群組(Group 00-7F),,每一群組由256個平面(Plane 00-FF)組成,,每一平面有256行(Row 00-FF),每一行256個編碼位(Cell 00-FF),。所以,,每一平面包括65,536個字符位(Character Position 0000-FFFF)。 整個編碼字符集的每個字符都由4個字節(jié),,按“組-面-行-列”的順序表示,。所以UCS的可編碼空間為:128 × 256 × 256 × 256 = 231。 UCS將其第一個平面(00群組中的00平面)稱作基本多語種平面(Basic Multilingual Plane,,BMP),。
在UCS中,目前只有00組是重要的,,Unicode學(xué)術(shù)學(xué)會斷言,,在可以預(yù)見的將來,甚至不可能用完00組中的前17個平面(00平面到10平面),。因此,,Unicode只定義了ISO 10646的第00組的前17個平面。事實上,,目前絕大多數(shù)字符,,都分配在第00平面BMP中。
下表中列出了BMP中的字符分配情況:
UCS的表現(xiàn)形式UCS有兩種方式來表示一個字符編碼:四字節(jié)正規(guī)形式(UCS-4,,Four-octet canonical form)和雙字節(jié)基本平面形式(UCS-2,Two-octet BMP form),。 UCS-4 —— 四字節(jié)正規(guī)形式 UCS-4用4個字節(jié)來表示一個字符,。第一個字節(jié)表示組(Group),第二表示平面(Plane),,第三表示行(Row),,第四表示單元號或列(Cell)。 UCS-2 —— 雙字節(jié)基本平面形式 當(dāng)系統(tǒng)只使用BMP的字符碼時,,可以省略群組和平面中的八位,,將字符碼由32個位縮短為16個位(2個字節(jié))。標(biāo)記為UCS-2,。 Unicode和UCS-2同樣采用16位編碼,。所以一般可以把Unicode和UCS-2看作是同一樣?xùn)|西。 代理對(Surrogate Pair) UCS-4定義了4個字節(jié)表示一個字符,,用來應(yīng)付將來的擴(kuò)展是綽綽有余,。可是Unicode和UCS-2只定義了2個字節(jié),,卻很容易用盡,。代理對(Surrogate Pair)的設(shè)計在這種背景下應(yīng)運(yùn)而生。 UCS-2在BMP中開辟了一個特殊的區(qū)間(D800 - DFFF) -- 代理區(qū),,并平分成兩個區(qū),,分別稱為高半代理區(qū)(High-half Zone,,D800 - DBFF),和低半代理區(qū)(Low-half Zone,,DC00 - DFFF),,各有1024個碼位。使用時,,從高低兩個代理區(qū)中各取一個編碼組成一個四字節(jié)的代理,,來表示一個在BMP以外平面上的編碼字符位。這樣一來,,總共可以多表示1024×1024個字符,,映射到00群組中的01到10平面(共16個平面)。 代理對提供了用BMP的2字節(jié)編碼來表示在基本多文種平面(BMP)之外的16個平面編碼的機(jī)制,。一些不常用的字符可以用代理對表示,。目前,只有ISO/IEC 10646-2:2001和Unicode 3.1才使用到代理對,。 高半代理區(qū)和低半代理區(qū)的劃分,使編碼位相互區(qū)分開,。非代理區(qū)字符一定不會在這個區(qū)里,。因為高半代理區(qū)和低半代理區(qū)不相交,所以很容易決定字符值的邊界,。一個完好的文本中,,高半代理碼和低半代理碼總是按先后成對出現(xiàn)。 如果在實現(xiàn)上沒有刪除代理碼或在代理碼對中插入字符,,數(shù)據(jù)的完整性就可得到保證,。即使數(shù)據(jù)有殘損,也只是局部的,。一個殘缺的碼只影響一個字符,。因為高半代理區(qū)和低半代理區(qū)不相交,,且成對出現(xiàn),錯碼不會傳到文本的其它部分。 具體來說,,一個代理對(H,L)由碼值為D800-DBFF 的高半代理碼H和碼值為 DC00-DFFF低半代理碼L組成,。將一個字符映射到UCS-4碼位中,。假設(shè)N是UCS-4碼值,則有:(以下所有數(shù)字均為16進(jìn)制) N = (H - D800) × 400 + (L - DC00) + 10000 于是得到N的碼值為10000到10FFFF,。 注意 Unicode 3.0沒有用到代理對,,直到3.1才增加了CJK Ext B,用到了02平面,,需要使用代理對才能訪問,。但99.99%的情況下,,根本用不到那些字。此外,,JDK1.4只支持到Unicode 3.0,,所以目前Java還不能應(yīng)用代理對。 UTF編碼UTF為UCS Transformation Format的縮寫,,意為“UCS轉(zhuǎn)換格式”,。UCS只是一個字形和內(nèi)碼上的標(biāo)準(zhǔn),并沒有定義實際在計算機(jī)上存取的方法,,而UTF便定義了一整套的計算機(jī)存取UCS編碼的轉(zhuǎn)換格式,,并考慮了與其它編碼方式兼容。常用的格式有UTF-8和UTF-16,。有時也用到UTF-7來進(jìn)行7位數(shù)據(jù)傳輸,。 UTF-16 UTF-16是用定長16位(2字節(jié))來表示的UCS-2或Unicode轉(zhuǎn)換格式。它將Unicode的編碼值變成2字節(jié)的Big-endian(高位字節(jié)在前,,低位字節(jié)在后)或Little-endian(低位字節(jié)在前,,高位字節(jié)在后)編碼。UTF-16利用代理對來訪問BMP之外的字符編碼,。 Java使用Big-endian系統(tǒng),,而Intel系列處理器內(nèi)部使用Little-endian系統(tǒng)(學(xué)匯編語言和C語言的人都知道)。 例如:“中國”兩字,,Unicode是4E2D 56FD,,在Windows上用UTF-16編碼,結(jié)果為四個字節(jié):2D 4E FD 56,;如果使用Java輸出,,結(jié)果為:4E 2D 56 FD。 使用UTF-16有什么缺點呢,?很顯然,, 1. 所有原本1個字節(jié)就可以表示的西方字符,現(xiàn)在要用2個字節(jié)來表示,,體積大了一倍,。 2. 學(xué)過C的人都知道,0x00代表C字符串的結(jié)尾,。但是用UTF-16來表示單字節(jié)字符(ISO-8859-1)時,,高位字節(jié)為0x00。這樣就會使C語言庫函數(shù)發(fā)生誤判,。用UTF-16表示文件名,、網(wǎng)址等,全引出無數(shù)的問題,。 3. 字符的邊界不好找,。程序處理時必須從字符串的頭部開始掃描,,才可能正確地找出一個字符的邊界,效率較低,。此外,,萬一壞掉一個字節(jié),這個字節(jié)之后的字符都會錯位,,壞掉一片,。 所有的這些問題,在UTF-8中都不存在,。 但是,,UTF-16也有其天然的優(yōu)點:它直接表現(xiàn)了字符編碼的整數(shù)值。所以UTF-16是最直接的Unicode表示法,。此外,,它是定長的,這大大簡化了字符串的操作,。Java語言就是用UTF-16格式將字符存儲在內(nèi)存中的,。正是這樣,才使Java的Unicode字符串的操作格外簡單高效,。 UTF-8 UTF-8使用了變長技術(shù),,在每一個編碼區(qū)域有不同的字碼長度: 1. 對UCS-2,由1字節(jié)至3字節(jié)構(gòu)成,; 2. 如果UCS-2使用了代理對,則UTF-8最長可到4字節(jié),; 3. 對UCS-4,,由1字節(jié)至6字節(jié)構(gòu)成。 因為以字節(jié)(8位)為組成單元,,故稱為“UTF-8”,。對于英文文本,UTF-8的文件大小比其它轉(zhuǎn)換格式都小,。 在UTF-8內(nèi),,字符由1個至6個字節(jié)為組合。下表列舉出了不同范圍的UCS碼轉(zhuǎn)換成UTF-8的規(guī)則,。英文字母“x”代表可以用來記錄 Unicode 碼值的區(qū)域,。
在UTF-8內(nèi), 1. 如果一個字節(jié),,最高位(第8位)為0,,表示這是一個ASCII字符(00 - 7F)??梢?,所有ASCII編碼已經(jīng)是UTF-8了,。 2. 如果一個字節(jié),以11開頭,,連續(xù)的1的個數(shù)暗示這個字符的字節(jié)數(shù),,例如:110xxxxx代表它是雙字節(jié)UTF-8字符的首字節(jié)。 3. 如果一個字節(jié),,以10開始,,表示它不是首字節(jié),需要向前查找才能得到當(dāng)前字符的首字節(jié),。 可見UTF-8可以有效地保證數(shù)據(jù)的完整性,,避免出現(xiàn)編碼的錯位。即使偶然出現(xiàn)“壞字”,,也不會影響到后續(xù)的文本,。 那么UTF-8有什么缺點呢?顯然,,對于在BMP中的中文字來說,,需要用3個字節(jié)才能表示,比使用UTF-16或直接使用雙字節(jié)的GB2312編碼大了0.5倍,。
上文說了一大通,,總結(jié)一下,其實很簡單:
|
|