字符編碼標準

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

作者序

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

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

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

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

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

字符編碼簡介

ASCII碼

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

單字節編碼的發展

在80年代早期,一些現在流行的標準(如ISO 8859Unicode)還未出現。那時爲了支持多種地區的語言,各大組織機構或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系列標準的字符編碼,還是互不相容,不可能同時使用的。畢竟它們只是單字節的編碼方案。而且,它們和多字節的編碼方案如中文編碼GB2312BIG5也是不相容的。那些歐洲字符(最高位爲1的字符),在GB2312BIG5中被認爲是雙字節漢字編碼的首字節。

多字節編碼的發展

單字節編碼只有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年發佈,是中文信息處理的國家標準,在大陸及海外使用簡體中文的地區(如新加坡等)是強制使用的唯一中文編碼。

他由6763個常用漢字和682個全角的非漢字字符組成。其中漢字根據使用的頻率分爲兩級。一級漢字3755個,二級漢字3008個。由於字符數量比較大,GB2312採用了二維矩陣編碼法對所有字符進行編碼。首先構造一個94行94列的方陣,對每一行稱爲一個“區”,每一列稱爲一個“位”,然後將所有字符依照下表的規律填寫到方陣中。這樣所有的字符在方陣中都有一個唯一的位置,這個位置可以用區號、位號合成表示,稱爲字符的區位碼。如第一個漢字“啊”出現在第16區的第1位上,其區位碼爲1601。因爲區位碼同字符的位置是完全對應的,因此區位碼同字符之間也是一一對應的。這樣所有的字符都可通過其區位碼轉換爲數字編碼信息。GB2312字符的排列分佈情況如下: 

分區範圍 符號類型
第01區 中文標點、數學符號以及一些特殊字符
第02區 各種各樣的數學序號
第03區 全角西文字符
第04區 日文平假名
第05區 日文片假名
第06區 希臘字母表
第07區 俄文字母表
第08區 中文拼音字母表
第09區 製表符號
第10-15區 無字符
第16-55區 一級漢字(以拼音字母排序)
第56-87區 二級漢字(以部首筆畫排序)
第88-94區 無字符
GB2312字符在計算機中存儲是以其區位碼爲基礎的,其中漢字的區碼和位碼分別佔一個存儲單元,每個漢字佔兩個存儲單元。由於區碼和位碼的取值範圍都是在1-94之間,這樣的範圍同西文的存儲表示衝突。例如漢字‘珀’在GB2312中的區位碼爲7174,其兩字節表示形式爲71,74;而兩個西文字符‘GJ’的存儲碼也是71,74。這種衝突將導致在解釋編碼時到底表示的是一個漢字還是兩個西文字符將無法判斷。爲避免同西文的存儲發生衝突,GB2312字符在進行存儲時,通過將原來的每個字節第8bit設置爲1同西文加以區別,如果第8bit爲0,則表示西文字符,否則表示GB2312中的字符。實際存儲時,採用了將區位碼的每個字節分別加上A0H(160)的方法轉換爲存儲碼,計算機存儲規則是此編碼的補碼,而且是位碼在前,區碼在後。例如漢字‘啊’的區位碼爲1601,其存儲碼爲B0A1H,其轉換過程爲:
區位碼 區碼轉換 位碼轉換 存儲碼
1001H 10H+A0H=B0H 01H+A0H=A1H B0A1H

·         雙字節編碼,範圍:B0A0 ~ F7FE(首字節在B0-F7 之間,尾字節在A0-FE 之間)。

GBK

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

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

·         範圍:8140 ~ FEFE(首字節在81-FE 之間,尾字節在40-FE 之間,剔除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 UnicodeConsortium)的機構制定的字符編碼系統。UnicodeISO 10646國際編碼標準從內容上來說是同步一致的。

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

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

Unicode和ISO 10646的關係

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

ISO 10646UCS

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中。

雖然兩個組織保持如此密切的合作關係,但UnicodeISO 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

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

代理對(Surrogate Pair)

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

UCS-2BMP中開闢了一個特殊的區間(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:2001Unicode 3.1才使用到代理對。

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

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

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

N = (H - D800) × 0400 + (L - DC00) + 01 0000

於是得到N的碼值爲01 0000到10 FFFF。

注意

Unicode 3.0沒有用到代理對,直到3.1才增加了CJK Ext B,用到了02平面,需要使用代理對才能訪問。但99.99%的情況下,根本用不到那些字。此外,JDK1.4只支持到Unicode 3.0,所以目前Java還不能應用代理對。

UTF編碼

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

UTF-16

UTF-16是用定長16位(2字節)來表示的UCS-2Unicode轉換格式。它將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)最多可以表示從00 0000到01 FFFF的字符。然而絕大多數情況下,只需要用到0000到FFFF之間的字符就足夠了。
  5. Unicode常用UTF-8UTF-16來表示。7位的ASCII碼不用作任何變化,就已經是UTF-8了。但UTF-8需要用3個字節來表示一個漢字。
  6. ISO 8859系列字符集,定義了單字節字符編碼的標準。其中最特殊的是ISO-8859-1編碼,它的編碼和Unicode中最開始的256個字符編碼完全相同。
  7. GB18030編碼是中國大陸的國家標準,在字彙上等同於Unicode,在編碼上和GB2312編碼以及GBK編碼兼容。

更詳細介紹,請參閱:http://blog.csdn.net/rainday0310/article/details/8494808
發佈了113 篇原創文章 · 獲贊 329 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章