中文化和國際化問題權威解析之一:字元編碼發展歷程

前幾天看文初的《精武門之Web安全研討會首日感受》,說到利用字符集攻擊時提到以前寶寶寫的一篇有關國際化的文章,趁機再次拜讀了寶寶的這篇大作,不得不感慨寶寶的寫作功底,無敵!這麼好的文章不分享出來實在是太可惜了,在此將寶寶的大作轉帖於此;

作者序

在我開發Java程序的幾年中,遇到得最多,也是別人向我提問最多的問題,就是各種各樣看似稀奇古怪的中文亂碼問題了。網上也有許多解釋和解決Java中文問題的文章,但水平參差不齊,有一些文章甚至是錯誤的。

此外,我們公司自己的Java程序從一開始就採用了錯誤的方式處理中文問題,雖能解一時之急,卻引出了越來越多的深遠的問題。每當我聽到有的同事還在討論如何特殊處理雙位元組的中文GB碼,就感慨他們思路的狹隘。試問,今天我們可以用特殊的方式處理我們所熟悉的中文編碼,可是今後我們怎樣才能應付日文版、韓文版、或世界其它國家語言的產品開發呢?

在我看來,與其說這些問題是「中文化問題」,不如說是「國際化問題」。所謂的「漢化」這種說法已經隨時代遠去了。想想看,這個詞帶有明顯的小農經濟的色彩:自家漢化自家用,哪管世界變化多。經過漢化的軟體,常常意味著:版本落後、不兼容、不穩定。為什麼會這樣呢?根本原因是,從軟體的設計階段,就沒有考慮國際用戶的需要,沒有採用國際通用的標準。事後要彌補自然難上加難。

所以讓我們把眼光放開,想一想「國際化」。當然國際化的目的還是生產出「漢化」的軟體,但我們可以用同樣的方法「韓化」、「日化」、「阿拉伯化」,統稱為「本地化」 —— 這就是「國際化」的目的。國際化和本地化有兩個很體面的英文縮寫:I18n(Internationalization)和L10n(Localization)。

想要開發出國際化的軟體產品,首先要了解國際標準,而不是使用東拼西湊的權宜之計。本文首先從相關國際標準的討論切入,相信正確地理解和應用這些標準,所有的「中文化問題」或「國際化問題」都會迎刃而解。

字元編碼簡介

ASCII碼

從學計算機的那天開始,老師就告訴我們在計算機裡面,所有的英文字母都對應到一個數字編碼,這就是ASCII碼(American Standard Code for Information Interchange)。ASCII碼是很久很久以前(1968年)制定的。它只使用了一個8位位元組中的低7位,總共是127個編碼位。這樣的方案很快就不夠使用了。

單位元組編碼的發展

在80年代早期,一些現在流行的標準(如ISO 8859和Unicode)還未出現。那時為了支持多種地區的語言,各大組織機構或IT廠商開始發明它們自己的編碼方案,以便彌補ASCII編碼的不足。一時間,各種互不相容的字元編碼方案成百花齊放之勢。

為了避免混亂,ISO組織在1998年之後,陸續發表了一系列代號為8859的標準,作為ASCII編碼的標準擴展,終於統一了單位元組的西方字元的編碼。ISO是設在瑞士的國際標準化組織的簡稱(International Organization for Standardization)。

ISO-8859-1(Latin1 - 西歐字元)

ISO-8859-1覆蓋了大多數西歐語言,包括:法國、西班牙、葡萄牙、義大利、荷蘭、德國、丹麥、瑞典、挪威、芬蘭、冰島、愛爾蘭、蘇格蘭、英格蘭等,因而也涉及到了整個美洲大陸、澳大利亞和非洲很多國家的語言。

此外,ISO-8859-1後來被採納為ISO-10646標準(後面會講到)的首頁,換句話說,Unicode的最開頭256個字元編碼和ISO-8859-1是一一對應的。正是由於這個特殊性,使很多人產生了對ISO-8859-1編碼的誤用。

ISO-8859標準還包括:

  • ISO-8859-2(Latin2 - 中、東歐字元)
  • ISO-8859-3(Latin3 - 南歐字元)
  • ISO-8859-4(Latin4 - 北歐字元)
  • ISO-8859-5(Cyrillic - 斯拉夫語)
  • ISO-8859-6(Arabic - 阿拉伯語)
  • ISO-8859-7(Greek - 希臘語)
  • ISO-8859-8(Hebrew - 希伯來語)
  • ISO-8859-9(Latin5)
  • ISO-8859-10(Latin6)
  • ISO-8859-11(Thai - 泰國語)
  • ISO-8859-12(保留)
  • ISO-8859-13(Latin7)
  • ISO-8859-14(Latin8)
  • ISO-8859-15(Latin9)
  • 但是ISO 8859系列標準的字元編碼,還是互不相容,不可能同時使用的。畢竟它們只是單位元組的編碼方案。而且,它們和多位元組的編碼方案如中文編碼GB2312和BIG5也是不相容的。那些歐洲字元(最高位為1的字元),在GB2312和BIG5中被認為是雙位元組漢字編碼的首位元組。

    多位元組編碼的發展

    單位元組編碼只有256個碼位(28=256),而中文字元何止千千萬,單位元組編碼不可能滿足中文編碼的需要。於是為了適應東方文字信息處理的需要,ISO又制定了ISO 2022標準(Character code structure and extension techniques),提供了七位與八位編碼字符集的擴充方法的標準。我國根據ISO 2022制定了國家標準GB2311 ——《信息交換用七位編碼字符集的擴充方法》,並根據該標準制定了國家標準GB2312-80編碼。其他東方國家和地區也制定了各自的字元編碼標準,如日本的JIS0208,韓國的KSC5601,台灣地區的CNS11643等。

    BIG5

    BIG5是從CNS11643的早期版本發展而來的,雖然沒有包括CNS11643的全部內容,但卻是目前台灣、香港地區普遍使用的一種繁體漢字的市場標準,包括440個符號,一級漢字5401個、二級漢字7652個,共計13060個漢字。

    GB2312-80

    全稱是《信息交換用漢字編碼字符集 基本集》,1980年發布,是中文信息處理的國家標準,在大陸及海外使用簡體中文的地區(如新加坡等)是強制使用的唯一中文編碼。

    · 雙位元組編碼

    · A1-A9:符號區,包含682個符號

    · B0-F7:漢字區,包含6763個漢字

    GB2312碼共收錄6763個簡體漢字、682個符號,其中漢字部分:一級字3755,以拼音排序,二級字3008,以偏旁排序。該標準的制定和應用為規範、推動中文信息化進程起了很大作用。

    GBK

    漢字內碼擴展規範(GBK)是國家技術監督局1995年為中文Windows 95所制定的新的漢字內碼規範。

    · 雙位元組編碼,GB2312-80的擴充,在碼位上和GB2312-80兼容。

    · 範圍:8140 ~ FEFE(剔除xx7F)共23940個碼位。

    · 包含21003個漢字,包含了ISO 10646中的全部中日韓漢字,簡、繁體字融於一庫。

    嚴格說,GBK不能算是國家標準,最多算是一個商業標準。而GB18030才是真正的國家標準。

    GB18030-2000

    全稱是《信息交換用漢字編碼字符集》,是我國的強制標準,所有不支持GB18030標準的軟體將不能作為產品出售。

    · 單位元組、雙位元組、四位元組編碼。

    · 向下與GB2312編碼兼容。

    · 支持GB 13000.1-1993中的全部中、日、韓(CJK)統一漢字字元和全部CJK統一漢字擴展A的字元。

    雖然GB18030標準非常強大,但它是一個中國大陸的標準。在編碼上,除了和GB2312以外,還是不能和世界上其它任何一種字元編碼統一。

    終極標準 —— Unicode和ISO 10646

    前面所講的一切字元編碼方案,都是針對局部地區或少數語言文字的,沒有辦法同時表達所有的語言文字,或在多種語言平台上交換。這對今天極其頻繁的國際信息交流是不相稱的。

    為了提高計算機的信息處理和交換功能,使得世界各國的文字都能在計算機中處理,從1984年起,ISO組織就開始研究制定一個全新的標準:通用多八位編碼字符集(Universal Multiple-Octet Coded Character Set),簡稱UCS。標準的編號為:ISO 10646。這一標準為世界各種主要語言的字元(包括簡體及繁體的中文字)及附加符號,編製統一的內碼。

    統一碼(Unicode)是Universal Code的縮寫,是由另一個叫「Unicode學術學會」(The Unicode Consortium)的機構制定的字元編碼系統。Unicode與ISO 10646國際編碼標準從內容上來說是同步一致的。

    Unicode是Java語言和XML的基礎,所以我們要稍微詳細地介紹一下Unicode以及ISO 10646標準。

    注意:不夠耐心的讀者可以跳過本章的餘下部分。但顯然了解本章所描述的Unicode及相關編碼的技術細節,有利於你更好地理解和應用Unicode。

    Unicode和ISO 10646的關係

    在1991年,Unicode學術學會與ISO國際標準化組織決定共同制訂一套適用於多種語言文本的通用編碼標準。Unicode與ISO 10646國際編碼標準於1992年1月正式合作發展一套通用編碼標準。自此,兩個組織便一直緊密合作,同步發展Unicode及ISO 10646國際編碼標準。

    ISO 10646(UCS)

    Unicode

    1993年,ISO組織發表ISO 10646國際編碼標準的第一個版本,全名是ISO/IEC 10646-1:1993。它收錄了20902個表意字元(ideograph,中日韓文均屬表意字元)。

    同年,Unicode學術學會根據ISO/IEC 10646-1:1993修訂了Unicode 1.0,發布Unicode 1.1。

    不斷改善和修訂ISO 10646標準。

    1996年發表Unicode 2.0,1998年發表Unicode 2.1,根據ISO 10646做了一些改善和修訂,新增了歐元符號。

    2000年10月發表了ISO 10646第二版的第一部分:ISO/IEC 10646-1:2000,新增收了6,582個表意字元於擴展區A中(CJK Unified Ideographs Extension A)。

    2000年2月,發表Unicode 3.0,也包含了同樣的CJK Ext A。

    2001年,發表了ISO/IEC 10646的第二部分,增收了42711個表意字元於擴展區B里。

    2001年,Unicode發表3.1版,將CJK Ext B納入新版Unicode中。

    雖然兩個組織保持如此密切的合作關係,但Unicode和ISO 10646還是有區別的。ISO 10646著重定義字元編碼,而Unicode則在此基礎上,為這些字元及編碼數據提出應用的方法以及對語義數據作補充。

    UCS的結構

    UCS的結構是一個四維的編碼空間,每一維由一個位元組(八位二進位位)組成,範圍是00到FF。總體上分為128個群組(Group 00-7F),每一群組由256個平面(Plane 00-FF)組成,每一平面有256行(Row 00-FF),每一行256個編碼位(Cell 00-FF)。所以,每一平面包括65,536個字元位(Character Position 0000-FFFF)。

    整個編碼字符集的每個字元都由4個位元組,按「組-面-行-列」的順序表示。所以UCS的可編碼空間為:128 × 256 × 256 × 256 = 231

    UCS將其第一個平面(00群組中的00平面)稱作基本多語種平面(Basic Multilingual Plane,BMP)。

    在UCS中,目前只有00組是重要的,Unicode學術學會斷言,在可以預見的將來,甚至不可能用完00組中的前17個平面(00平面到10平面)。因此,Unicode只定義了ISO 10646的第00組的前17個平面。事實上,目前絕大多數字元,都分配在第00平面BMP中。

    下表中列出了BMP中的字元分配情況:

    區間

    描述

    (0000-1FFF)基本拼音字元區

    包括所有拼讀文字的字母拼音和音標。它的字符集一般較小,如:拉丁文、西里爾文、希臘文、希伯來文、阿拉伯文、泰文、天成文書(梵文)等。

    (2000-28FF)符號區

    包括許多種用於標點、數學、化學、科技及其它特殊用途上的「符號」和「丁貝符」(示意圖形符號)。

    (2E80-33FF)中日韓語音及符號區

    包括用於中國、日本、韓國語言中的標點、符號、字根(筆畫)及發音等字元。

    (3400-9FA5)中日韓漢字字元區

    由27,484個中日韓(越)的統一漢字組成。

    (A000-A4C6)彝族字元區

    由1,165個中國南方彝族音節和50個其字根組成。

    (AC00-D7A3)韓字元拼音區

    由11,172個預先組合的韓字元拼音音節組成。

    (D800-DFFF)代理區

    這個區被平分為1024個「高半代理區」(D800-DBFF)碼位和1024個「低半代理區」(DC00-DFFF)碼位,用來形成代理對,可以得到超過一百萬個擴充編碼位。

    (E000-F8FF)私人專用區

    包含6,400個編碼位,用於用戶或開發商自行定義的字元編碼。

    (F900-FA2D)兼容字元區

    包括一些被許多行業協會和國家標準廣泛使用的字元,但在Unicode編碼中有不同的表現形式。包含一些專用字元。

    UCS的表現形式

    UCS有兩種方式來表示一個字元編碼:四位元組正規形式(UCS-4,Four-octet canonical form)和雙位元組基本平面形式(UCS-2,Two-octet BMP form)。

    UCS-4 —— 四位元組正規形式

    UCS-4用4個位元組來表示一個字元。第一個位元組表示組(Group),第二表示平面(Plane),第三表示行(Row),第四表示單元號或列(Cell)。

    UCS-2 —— 雙位元組基本平面形式

    當系統只使用BMP的字元碼時,可以省略群組和平面中的八位,將字元碼由32個位縮短為16個位(2個位元組)。標記為UCS-2。

    Unicode和UCS-2同樣採用16位編碼。所以一般可以把Unicode和UCS-2看作是同一樣東西。

    代理對(Surrogate Pair)

    UCS-4定義了4個位元組表示一個字元,用來應付將來的擴展是綽綽有餘。可是Unicode和UCS-2隻定義了2個位元組,卻很容易用盡。代理對(Surrogate Pair)的設計在這種背景下應運而生。

    UCS-2在BMP中開闢了一個特殊的區間(D800 - DFFF) -- 代理區,並平分成兩個區,分別稱為高半代理區(High-half Zone,D800 - DBFF),和低半代理區(Low-half Zone,DC00 - DFFF),各有1024個碼位。使用時,從高低兩個代理區中各取一個編碼組成一個四位元組的代理,來表示一個在BMP以外平面上的編碼字元位。這樣一來,總共可以多表示1024×1024個字元,映射到00群組中的01到10平面(共16個平面)。

    代理對提供了用BMP的2位元組編碼來表示在基本多文種平面(BMP)之外的16個平面編碼的機制。一些不常用的字元可以用代理對表示。目前,只有ISO/IEC 10646-2:2001和Unicode 3.1才使用到代理對。

    高半代理區和低半代理區的劃分,使編碼位相互區分開。非代理區字元一定不會在這個區里。因為高半代理區和低半代理區不相交,所以很容易決定字元值的邊界。一個完好的文本中,高半代理碼和低半代理碼總是按先後成對出現。

    如果在實現上沒有刪除代理碼或在代理碼對中插入字元,數據的完整性就可得到保證。即使數據有殘損,也只是局部的。一個殘缺的碼隻影響一個字元。因為高半代理區和低半代理區不相交,且成對出現,錯碼不會傳到文本的其它部分。

    具體來說,一個代理對(H,L)由碼值為D800-DBFF 的高半代理碼H和碼值為 DC00-DFFF低半代理碼L組成。將一個字元映射到UCS-4碼位中。假設N是UCS-4碼值,則有:(以下所有數字均為16進位)

    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還不能應用代理對。

    UTF編碼

    UTF為UCS Transformation Format的縮寫,意為「UCS轉換格式」。UCS只是一個字形和內碼上的標準,並沒有定義實際在計算機上存取的方法,而UTF便定義了一整套的計算機存取UCS編碼的轉換格式,並考慮了與其它編碼方式兼容。常用的格式有UTF-8和UTF-16。有時也用到UTF-7來進行7位數據傳輸。

    UTF-16

    UTF-16是用定長16位(2位元組)來表示的UCS-2或Unicode轉換格式。它將Unicode的編碼值變成2位元組的Big-endian(高位位元組在前,低位位元組在後)或Little-endian(低位位元組在前,高位位元組在後)編碼。UTF-16利用代理對來訪問BMP之外的字元編碼。

    Java使用Big-endian系統,而Intel系列處理器內部使用Little-endian系統(學彙編語言和C語言的人都知道)。

    例如:「中國」兩字,Unicode是4E2D 56FD,在Windows上用UTF-16編碼,結果為四個位元組:2D 4E FD 56;如果使用Java輸出,結果為:4E 2D 56 FD。

    使用UTF-16有什麼缺點呢?很顯然,

    1. 所有原本1個位元組就可以表示的西方字元,現在要用2個位元組來表示,體積大了一倍。

    2. 學過C的人都知道,0x00代表C字元串的結尾。但是用UTF-16來表示單位元組字元(ISO-8859-1)時,高位位元組為0x00。這樣就會使C語言庫函數發生誤判。用UTF-16表示文件名、網址等,全引出無數的問題。

    3. 字元的邊界不好找。程序處理時必須從字元串的頭部開始掃描,才可能正確地找出一個字元的邊界,效率較低。此外,萬一壞掉一個位元組,這個位元組之後的字元都會錯位,壞掉一片。

    所有的這些問題,在UTF-8中都不存在。

    但是,UTF-16也有其天然的優點:它直接表現了字元編碼的整數值。所以UTF-16是最直接的Unicode表示法。此外,它是定長的,這大大簡化了字元串的操作。Java語言就是用UTF-16格式將字元存儲在內存中的。正是這樣,才使Java的Unicode字元串的操作格外簡單高效。

    UTF-8

    UTF-8使用了變長技術,在每一個編碼區域有不同的字碼長度:

    1. 對UCS-2,由1位元組至3位元組構成;

    2. 如果UCS-2使用了代理對,則UTF-8最長可到4位元組;

    3. 對UCS-4,由1位元組至6位元組構成。

    因為以位元組(8位)為組成單元,故稱為「UTF-8」。對於英文文本,UTF-8的文件大小比其它轉換格式都小。

    在UTF-8內,字元由1個至6個位元組為組合。下表列舉出了不同範圍的UCS碼轉換成UTF-8的規則。英文字母「x」代表可以用來記錄 Unicode 碼值的區域。

    UCS-4 區域(十六進位)

    UTF-8位元組組合(二進位)

    0000 0000 —— 0000 007F

    0xxxxxxx

    0000 0080 —— 0000 07FF

    110xxxxx 10xxxxxx

    0000 0800 —— 0000 FFFF

    1110xxxx 10xxxxxx 10xxxxxx

    0001 0000 —— 001F FFFF

    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

    0020 0000 —— 03FF FFFF

    111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

    0400 0000 —— 7FFF FFFF

    1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

    在UTF-8內,

    1. 如果一個位元組,最高位(第8位)為0,表示這是一個ASCII字元(00 - 7F)。可見,所有ASCII編碼已經是UTF-8了。

    2. 如果一個位元組,以11開頭,連續的1的個數暗示這個字元的位元組數,例如:110xxxxx代表它是雙位元組UTF-8字元的首位元組。

    3. 如果一個位元組,以10開始,表示它不是首位元組,需要向前查找才能得到當前字元的首位元組。

    可見UTF-8可以有效地保證數據的完整性,避免出現編碼的錯位。即使偶然出現「壞字」,也不會影響到後續的文本。

    那麼UTF-8有什麼缺點呢?顯然,對於在BMP中的中文字來說,需要用3個位元組才能表示,比使用UTF-16或直接使用雙位元組的GB2312編碼大了0.5倍。

    上文說了一大通,總結一下,其實很簡單:

    1. 字元編碼是抽象字元在計算機中的數字表示。
    2. 字元編碼集(character set,簡稱字符集)是一批字元編碼的集合。世界上存在大量互不兼容的字符集,給國際交流帶來了困難。
    3. ASCII碼是最古老的字元編碼,它總共只定義了7位共128個字母、數字和符號。但它是其它所有字元編碼的基礎。
    4. Unicode用16位整數編碼,將世界上所有主要文字的字元統一起來了。如果利用代理對(surrogate pair)最多可以表示從0到1FFFF的字元。然而絕大多數情況下,只需要用到0到FFFF之間的字元就足夠了。
    5. Unicode常用UTF-8和UTF-16來表示。7位的ASCII碼不用作任何變化,就已經是UTF-8了。但UTF-8需要用3個位元組來表示一個漢字。
    6. ISO 8859系列字符集,定義了單位元組字元編碼的標準。其中最特殊的是ISO-8859-1編碼,它的編碼和Unicode中最開始的256個字元編碼完全相同。
    7. GB18030編碼是中國大陸的國家標準,在字彙上等同於Unicode,在編碼上和GB2312編碼以及GBK編碼兼容。

    推薦閱讀:

    相學解析:面相與命運之間的密切聯繫
    八字解析「傷官」見官斷送法則
    奇門測病解析
    全面解析中醫治療痤瘡
    曲煒解析《滴天髓》順逆、寒暖論 最具精髓價值之論

    TAG:文化 | 發展 | 字元編碼 | 國際 | 國際化 | 編碼 | 解析 | 權威 | 字元 | 問題 | 中文 |