UNICODE與UTF-8的轉換詳解

<script type="text/javascript"> document.body.oncopy = function() { if (window.clipboardData) { setTimeout(function() { var text = clipboardData.getData("text"); if (text && text.length > 300) { text = text + "/r/n/n本文來自CSDN博客,轉載請標明出處:" + location.href; clipboardData.setData("text", text); } }, 100); } } </script>

一篇詳細的漢字編解碼方面的文章。

轉載自: http://www.ins1000.cn/KnowledgeActionForReader?action=read&id=104

UNICODE與UTF-8的轉換詳解

 

1 編碼
在計算機中,各種信息都是以二進制編碼的形式存在的,也就是說,不管 是文字、圖形、聲音、動畫,還是電影等各種信息,在計算機中都是以0和1組成的二進制代碼表示的。爲了區分這些信息,人們就爲計算機設計了一套識別準則, 這也就是計算機編碼。例如:英文字母與漢字的的區別,就是英文字母用的是單字節的ASCII碼,漢字採用的是雙字節的漢字內碼。
1.1 基本概念
* 字符:字符是抽象的最小文本單位。它沒有固定的形狀(可能是一個字形),而且沒有值。“A”是一個字符,“€”(德國、法國和許多其他歐洲國家通用貨幣的標誌)也是一個字符。
* 字符集:字符集是字符的集合。例如,漢字字符是中國人最先發明的字符,在中文、日文、韓文和越南文的書寫中使用。
* 編碼字符集:編碼字符集是一個字符集,它爲每一個字符分配一個唯一數字。Unicode 標準的核心是一個編碼字符集,字母“A”的編碼爲 004116 和字符“€”的編碼爲 20AC16。Unicode 標準始終使用十六進制數字,而且在書寫時在前面加上前綴“U+”,所以“A”的編碼書寫爲“U+0041”。
* 代碼點:代碼點是指可用於編碼字符集的數字。編碼字符集定義一個有效的代碼點範圍,但是並不一定將字符分配給所有這些代碼點。有效的Unicode代碼點 範圍是 U+0000 至U+10FFFF。Unicode4.0將字符分配給一百多萬個代碼點中的96,382代碼點。
* 增補字符:增補字符是代碼點在 U+10000 至 U+10FFFF 範圍之間的字符,也就是那些使用原始的Unicode的16 位設計無法表示的字符。從U+0000至 U+FFFF之間的字符集有時候被稱爲基本多語言面 (BMP)。因此,每一個Unicode 字符要麼屬於BMP,要麼屬於增補字符。
* 字符編碼方案:字符編碼方案是從一個或多個編碼字符集到一個或多個固定寬度代碼單元序列的映射。最常用的代碼單元是字節,但是 16 位或 32 位整數也可用於內部處理。UTF-32、UTF-16 和 UTF-8 是 Unicode 標準的編碼字符集的字符編碼方案。
* UTF-32:即將每一個 Unicode 代碼點表示爲相同值的 32 位整數。很明顯,它是內部處理最方便的表達方式,但是,如果作爲一般字符串表達方式,則要消耗更多的內存。
* UTF-16:使用一個或兩個未分配的 16 位代碼單元的序列對Unicode代碼點進行編碼。值U+0000至U+FFFF編碼爲一個相同值的16位單元。增補字符編碼爲兩個代碼單元,第一個單元 來自於高代理範圍(U+D800 至 U+DBFF),第二個單元來自於低代理範圍(U+DC00至U+DFFF)。這在概念上可能看起來類似於多字節編碼,但是其中有一個重要區別:值 U+D800至U+DFFF 保留用於UTF-16;沒有這些值分配字符作爲代碼點。這意味着,對於一個字符串中的每個單獨的代碼單元,軟件可以識別是否該代碼單元表示某個單單元字 符,或者是否該代碼單元是某個雙單元字符的第一個或第二單元。這相當於某些傳統的多字節字符編碼來說是一個顯著的改進,在傳統的多字節字符編碼中,字節值 0x41既可能表示字母“A”,也可能是一個雙字節字符的第二個字節。
* UTF-8:使用一至四個字節的序列對編碼Unicode代碼點進行編碼。U+0000至U+007F 使用一個字節編碼,U+0080至U+07FF使用兩個字節,U+0800至U+FFFF 使用三個字節,而U+10000至U+10FFFF使用四個字節。UTF-8設計原理爲:字節值0x00至0x7F 始終表示代碼點U+0000至U+007F(Basic Latin 字符子集,它對應 ASCII 字符集)。這些字節值永遠不會表示其他代碼點,這一特性使UTF-8可以很方便地在軟件中將特殊的含義賦予某些ASCII字符。
(表1-1 編碼的一些基本概念)
1.2 字符編碼
字符編碼即英文編碼,包括字母、數字、標點、運算符等。
    字符的編碼採用國際通用的ASCII碼(American Standard Code for Information Interchange,美國信息交換標準代碼),每個ASCII碼以1個字節(Byte)儲存,從0到數字127代表不同的常用符號,例如大寫A的 ASCII碼是65,小寫a則是97。由於ASCII碼只用了字節的七個位,最高位並不使用,所以後來又將最高的一個位也編入這套編碼碼中,成爲八個位的 延伸ASCII(ExtendedASCII)碼,這套內碼加上了許多外文和表格等特殊符號,成爲目前常用的編碼。基本的ASCII字符集共有128個字 符,其中有96個可打印字符,包括常用的字母、數字、標點符號等,另外還有32個控制字符。標準ASCII碼使用7個二進位對字符進行編碼,對應的ISO 標準爲ISO646標準。
    字母和數字的ASCII碼的記憶是非常簡單的。我們只要記住了一個字母或數字的ASCII碼(例如記住A爲65,0的ASCII碼爲48),知道相應的大小寫字母之間差32,就可以推算出其餘字母、數字的ASCII碼。
    雖然標準ASCII碼是7位編碼,但由於計算機基本處理單位爲字節(1byte = 8bit),所以一般仍以一個字節來存放一個ASCII字符。每一個字節中多餘出來的一位(最高位)在計算機內部通常保持爲0(在數據傳輸時可用作奇偶校 驗位)。由於標準ASCII字符集字符數目有限,在實際應用中往往無法滿足要求。爲此,國際標準化組織又制定了ISO2022標準,它規定了在保持與 ISO646兼容的前提下將ASCII字符集擴充爲8位代碼的統一方法。ISO陸續制定了一批適用於不同地區的擴充ASCII字符集,每種擴充ASCII 字符集分別可以擴充128個字符,這些擴充字符的編碼均爲高位爲1的8位代碼(即十進制數128~255),稱爲擴展ASCII碼。
1.3 漢字編碼
1.3.1 漢字內碼
    漢字信息在計算機內部也是以二進制方式存放。由於漢字數量多,用一個字節的128種狀態不能全部表示出來,因此在1980年我國頒佈的《信息交換用漢字編 碼字符集——基本集》,即國家標準GB2312-80方案中規定用兩個字節的十六位二進制表示一個漢字,每個字節都只使用低7位(與ASCII碼相同), 即有128×128=16384種狀態。由於ASCII碼的34個控制代碼在漢字系統中也要使用,爲不致發生衝突,不能作爲漢字編碼,128除去34只剩 94種,所以漢字編碼表的大小是94×94=8836,用以表示國標碼規定的7445個漢字和圖形符號。
    每個漢字或圖形符號分別用兩位的十進制區碼(行碼)和兩位的十進制位碼(列碼)表示,不足的地方補0,組合起來就是區位碼。把區位碼按一定的規則轉換成的 二進制代碼叫做信息交換碼(簡稱國標碼)。國標碼共有漢字6763個(一級漢字,是最常用的漢字,按漢語拼音字母順序排列,共3755個;二級漢字,屬於 次常用漢字,按偏旁部首的筆劃順序排列,共3008個),數字、字母、符號等682個,共7445個。
    由於國標碼不能直接存儲在計算機內,爲方便計算機內部處理和存儲漢字,又區別於ASCII碼,將國標碼中的每個字節在最高位改設爲1,這樣就形成了在計算 機內部用來進行漢字的存儲、運算的編碼叫機內碼(或漢字內碼,或內碼)。內碼既與國標碼有簡單的對應關係,易於轉換,又與ASCII碼有明顯的區別,且有 統一的標準(內碼是惟一的)。
1.3.2 漢字外碼
    無論是區位碼或國標碼都不利於輸入漢字,爲方便漢字的輸入而制定的漢字編碼,稱爲漢字輸入碼,即漢字外碼。不同的輸入方法,形成了不同的漢字外碼。常見的輸入法有以下幾類:
* 按漢字的排列順序形成的編碼(流水碼):如區位碼;
* 按漢字的讀音形成的編碼(音碼):如全拼、簡拼、雙拼等;
* 按漢字的字形形成的編碼(形碼):如五筆字型、鄭碼等;
* 按漢字的音、形結合形成的編碼(音形碼):如自然碼、智能ABC。
* 輸入碼在計算機中必須轉換成機內碼,才能進行存儲和處理。
(表1-2 常見的輸入法)
1.3.3 漢字字形碼
    爲了將漢字在顯示器或打印機上輸出,把漢字按圖形符號設計成點陣圖,就得到了相應的點陣代碼(字形碼)。
    全部漢字字碼的集合叫漢字字庫。漢字庫可分爲軟字庫和硬字庫。軟字庫以文件的形式存放在硬盤上,現多用這種方式,硬字庫則將字庫固化在一個單獨的存儲芯片中,再和其它必要的器件組成接口卡,插接在計算機上,通常稱爲漢卡。
    用於顯示的字庫叫顯示字庫。顯示一個漢字一般採用16×16點陣或24×24點陣或48×48點陣。已知漢字點陣的大小,可以計算出存儲一個漢字所需佔用 的字節空間。例:用16×16點陣表示一個漢字,就是將每個漢字用16行,每行16個點表示,一個點需要1位二進制代碼,16個點需用16位二進制代碼 (即2個字節),共16行,所以需要16行×2字節/行=32字節,即16×16點陣表示一個漢字,字形碼需用32字節。即:字節數 = 點陣行數 * 點陣列數 / 8。
    用於打印的字庫叫打印字庫,其中的漢字比顯示字庫多,而且工作時也不像顯示字庫需調入內存。可以這樣理解,爲在計算機內表示漢字而統一的編碼方式形成漢字 編碼叫內碼(如國標碼),內碼是惟一的。爲方便漢字輸入而形成的漢字編碼爲輸入碼,屬於漢字的外碼,輸入碼因編碼方式不同而不同,是多種多樣的。爲顯示和 打印輸出漢字而形成的漢字編碼爲字形碼,計算機通過漢字內碼在字模庫中找出漢字的字形碼,實現其轉換。例如:
1:已知漢字"春"的國標碼爲343AH,求其機內碼?
答:機內碼 = 國標碼 + 8080H = 343AH + 8080H = B4BAH
 
2:用24×24點陣來表示一個漢字(一點爲一個二進制位),則2000個漢字需要多少KB容量?
答: 容量 = (24 * 24/8)* 2000 / 1024 = 140.7KB ≈ 141KB
(表1-3 漢字內碼與字形碼的轉換實例)
2 UNICODE編碼
Unicode 是基於通用字符集(Universal Character Set)的標準來發展,並且同時也以書本的形式(The Unicode Standard,目前第五版由Addison-Wesley Professional出版,ISBN-10: 0321480910)對外發表。Unicode包含了超過十萬個字符(在2005年,Unicode的第十萬個字符被採納且認可成爲標準之一)、一組可 用以作爲視覺參考的代碼圖表、一套編碼方法與一組標準字符編碼、一套包含了上標字、下標字等字符特性的列舉等。
在計算機科學領域中,Unicode(統一碼、萬國碼、單一碼、標準 萬國碼)是業界的一種標準,它可以使電腦得以呈現世界上數十種文字的系統。Unicode組織(The Unicode Consortium)是由一個非營利性的機構所運作,並主導Unicode的後續發展,其目標在於:將既有的字符編碼方案,以Unicode編碼方案來 加以取代,特別是既有的方案在多語環境下,皆僅有有限的空間以及不相容的問題。
Unicode在字符集認可的成功,使其得以在電腦軟件的國際化與本地化領域中,廣泛且具優勢的被採用。這標準已在近年來的多種新科技當中被加以採用,包含了可擴展置標語言(XML)、Java編程語言、以及最新的操作系統中。
2.2 UNICODE的起源與發展
Unicode是由於傳統的字符編碼方式的侷限性而產生的,例如 ISO 8859所定義的字符雖然在不同的國家中廣泛地使用,可是在不同國家間卻經常出現不相容的情況。很多傳統的編碼方式都具有一個共通的問題,即其容許電腦進 行雙語環境式的處理(通常使用拉丁字母以及其本地語言),但卻無法同時支援多語言環境式的處理(指可同時處理混合多種語言的情況)。
Unicode試圖將字位(字素,graphemes)與類字位字符加以認定與編碼,而非以不同的字形(glyphs)來加以區分。然而在漢字的個案來看,這樣方式有時會引起一字多形的認定爭議(詳見中日韓統一表意文字主題)。
在文字處理方面,Unicode的功用是爲每一個字符提供一個唯一的 代碼(即一組數字),而不是一種字形。換句話說,Unicode是將字符以一種抽象的方式來呈現,而將視覺上的演繹工作(例如字體大小、外觀形狀、字體形 態、文體等)留給其他軟件來處理,例如網頁瀏覽器或是文字處理器。
    爲了使Unicode與已存在和廣泛使用的舊有編碼互相兼容,尤其是差不多所有電腦系統都支援的基本拉丁字母部分,所以Unicode的首256字符仍舊 保留給ISO 8859-1所定義的字符,使既有的西歐語系文字的轉換不需特別考量;另方面因相同的原因,Unicode 把大量相同的字符重複編到不同的字符碼中去,使得舊有紛雜的編碼方式得以和 Unicode 編碼間互相直接轉換,而不會遺失任何資訊。舉例來說,全角格式區段包含了主要的拉丁字母的全角格式,在中文、日文、以及韓文字形當中,這些字符以全角的方 式來呈現,而不以常見的半角形式顯示,這對豎排文字和等寬排列文字有重要作用。
    Unicode組織位於美國加州,組織允許任何願意支付會員費用的公司或是個人加入,其成員包含了主要的電腦軟硬件廠商,例如奧多比系統(Adobe Systems)、蘋果公司(Apple)、惠普(HP)、IBM、微軟(Microsoft)、全錄(Xerox)等。Unicode 組織在 1991 年首次發佈了 The Unicode Standard(ISBN 0-321-18578-1)。Unicode的開發結合了國際標準化組織(International Organization for Standardization,簡稱 ISO)所制定的ISO/IEC 10646,即通用字符集(Universal Character Set,簡稱 UCS)。Unicode 與 ISO/IEC 10646 在編碼的運作原理相同,但 The Unicode Standard 包含了更詳盡的實現資訊、涵蓋了更細節的主題,諸如字符編碼(bitwise encoding)、校對以及呈現等。 The Unicode Standard 也列舉了諸多的字符特性,包含了那些必須支援雙方向呈現的文字(由左至右或由右至左的文字呈現方向,例如阿拉伯文是由右至左)。Unicode與 ISO/IEC 10646兩個標準在術語上的使用有些微的不同。
Unicode 截至目前爲止歷次的版次與發佈時間如下:
Unicode 1.0:1991年10月;
Unicode 1.0.1:1992年6月;
Unicode 1.1:1993年6月;
Unicode 2.0:1997年7月;
Unicode 2.1:1998年5月;
Unicode 2.1.2:1998年5月;
Unicode 3.0:1999年9月;涵蓋了來自ISO 10646-1的十六位元通用字符集(UCS)基本多文種平面(Basic Multilingual Plane);
Unicode 3.1:2001年3月;新增從ISO 10646-2定義的輔助平面(Supplementary Planes);
Unicode 3.2:2002年3月;
Unicode 4.0:2003年4月;
Unicode 4.0.1:2004年3月;
Unicode 4.1:2005年3月;
Unicode 5.0:2006年7月;
Unicode 5.1:2008年4月。
(表2-1 unicode編碼的版次與發佈時間)
2.3 Unicode 的編碼和實現
2.3.1 編碼方式
Unicode 的編碼方式與 ISO 10646 的通用字符集(Universal Character Set,UCS)概念相對應,目前實際應用的 Unicode 版本對應於 UCS-2,使用16位的編碼空間。也就是每個字符佔用2個字節。這樣理論上一共最多可以表示 216 即 65536 個字符。基本滿足各種語言的使用。實際上目前版本的 Unicode 尚未填充滿這16位編碼,保留了大量空間作爲特殊使用或將來擴展。
上述16位Unicode字符構成基本多文種平面(Basic Multilingual Plane,簡稱BMP)。最新(但未實際廣泛使用)的Unicode 版本定義了16個輔助平面,兩者合起來至少需要佔據21位的編碼空間,比3字節略少。但事實上輔助平面字符仍然佔用4字節編碼空間,與UCS-4保持一 致。未來版本會擴充到 ISO 10646-1 實現級別3,即涵蓋UCS-4的所有字符。UCS-4 是一個更大的尚未填充完全的31位字符集,加上恆爲0的首位,共需佔據32位,即4字節。理論上最多能表示231個字符,完全可以涵蓋一切語言所用的符 號。
BMP字符的Unicode編碼表示爲 U+hhhh,其中每個 h 代表一個十六進制數位。與UCS-2編碼完全相同。對應的4字節UCS-4編碼後兩個字節一致,前兩個字節的所有位均爲0。
2.3.2 實現方式
Unicode的實現方式不同於編碼方式。一個字符的Unicode 編碼是確定的。但是在實際傳輸過程中,由於不同系統平臺的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。 Unicode的實現方式稱爲Unicode轉換格式(Unicode Translation Format,簡稱爲 UTF)。
       例如,如果一個僅包含基本7位ASCII字符的Unicode文件,如果每個字符都使用2字節的原Unicode 編碼傳輸,其第一字節的8位始終爲0。這就造成了比較大的浪費。對於這種情況,可以使用 UTF-8 編碼,這是一種變長編碼,它將基本7位ASCII字符仍用7位編碼表示,佔用一個字節(首位補0)。而遇到與其他 Unicode 字符混合的情況,將按一定算法轉換,每個字符使用1-3個字節編碼,並利用首位爲0或1進行識別。這樣對以7位ASCII字符爲主的西文文檔就大大節省了 編碼長度。類似的,對未來會出現的需要4個字節的輔助平面字符和其他 UCS-4 擴充字符,2字節編碼的UTF-16也需要通過一定的算法進行轉換。
再如,如果直接使用與Unicode編碼一致(僅限於 BMP 字符)的UTF-16編碼,由於每個字符佔用了兩個字節,在Macintosh機和PC機上,對字節順序的理解是不一致的。這時同一字節流可能會被解釋爲 不同內容,如編碼爲U+594E的字符“奎”同編碼爲 U+4E59 的“乙”就可能發生混淆。於是在UTF-16編碼實現方式中使用了大尾序(big-endian)、小尾序(little-endian)的概念,以及 BOM(Byte Order Mark)解決方案。
       此外 Unicode 的實現方式還包括UTF-7、Punycode、CESU-8、SCSU、UTF-32等,這些實現方式有些僅在一定的國家和地區使用,有些則屬於未來的 規劃方式。目前通用的實現方式是 UTF-16小尾序(BOM)、UTF-16大尾序(BOM)和 UTF-8。在微軟公司Windows XP操作系統附帶的記事本中,“另存爲”對話框可以選擇的四種編碼方式除去非 Unicode 編碼的 ANSI 外,其餘三種“Unicode”、“Unicode big endian”和“UTF-8”即分別對應這三種實現方式。
 
3 UTF-8
UTF是UCS / Unicode Transformation Format(Unicode轉換格式)的縮寫,UTF-8(8位元Universal Character Set/Unicode Transformation Format)是一種針對 Unicode 的可變長度字符編碼。它可以用來表示 Unicode 標準中的任何字符,且其編碼中的第一個字節仍與ASCII相容,這使得原來處理ASCII字符的軟件無須或只須做少部份修改,即可繼續使用。因此,它逐漸 成爲電子郵件、網頁及其他儲存或傳送文字的應用中,優先採用的編碼。
3.1 UTF-8的歷史
1992年初,爲建立良好的字節串編碼系統(byte-stream encoding)以供多字節字符集(multi-byte character sets)使用,開始了一個正式的研究。ISO/IEC 10646的初稿中有一個非必須的附錄,名爲UTF。當中包含了一個供32位元的字符使用的字節串編碼系統。這個編碼方式的性能並不令人滿意,但它提出了 將0-127的範圍保留給ASCII以相容舊系統的概念。
1992年7月,X/Open委員會XoJIG開始尋求一個較佳的編 碼系統。Unix系統實驗室(UNIX System Laboratories, USL)的Dave Prosser爲此提出了一個編碼系統的建議。它具備可更快速實作的特性,並引入一項新的改進。其中,7位元的ASCII符號只代表原來的意思,所有多字 節序列則會包含第8位元的符號,也就是所謂的最高有效位元(Most significant bit)。
1992年8月,這個建議由IBMX/Open的代表流傳到一些感興 趣的團體。與此同時,貝爾實驗室Plan 9操作系統工作小組的肯·湯普遜對這編碼系統作出重大的修改,讓編碼可以自我同步(self-synchronizing),使得不必從字串的開首讀取, 也能找出字符間的分界。1992年9月2日,肯·湯普遜和Rob Pike一起在美國新澤西州一架餐車的餐桌墊上描繪出此設計的要點。接下來的日子,Pike及湯普遜將它實現,並將這編碼系統完全應用在Plan 9當中,及後他將有關成果回饋X/Open。
1993年1月25-29日的在聖地牙哥舉行的USENIX會議首次正式介紹UTF-8。
自1996年起,微軟的CAB(MS Cabinet)規格在UTF-8標準正式落實前就明確容許在任何地方使用UTF-8編碼系統。但有關的編碼器實際上從來沒有實作這方面的規格。
3.2 UTF-8的優缺點
3.2.1 UTF-8的特質
UTF-8 的設計有以下的多字符組序列的特質:
?單字節字符的最高有效位元永遠爲0;
?多字節序列中的首個字符組的幾個最高有效位元決定了序列的長度。最高有效位爲110的,是2字節序列,而1110的是三字節序列,如此類推;
?多字節序列中其餘的字節中的首兩個最高有效位元爲10。
(表3-1 UTF-8的特質)
UTF-8的這些特質,保證了一個字符的字節序列不會包含在另一個字 符的字節序列中。這確保了以字節爲基礎的部份字串比對(sub-string match)方法可以適用於在文字中搜尋字或詞。有些比較舊的可變長度8位元編碼(如Shift JIS)沒有這個特質,故字串比對的算法變得相當複雜。雖然這增加了UTF-8編碼的字串的信息冗餘,但是利多於弊。另外,資料壓縮並非Unicode的 目的,所以不可混爲一談。即使在傳送過程中有部份字節因錯誤或干擾而完全遺失,還是有可能在下一個字符的起點重新同步,令受損範圍受到限制。
       另一方面,由於其字節序列設計,如果一個疑似爲字符串的序列被驗證爲UTF-8編碼,那麼我們可以有把握地說它是UTF-8字符串。一段兩字節隨機序列碰 巧爲合法的UTF-8而非ASCII 的機率爲32分1。對於三字節序列的機率爲256分3,對更長的序列的機率就更低了。
3.2.2 UTF-8的優點
UTF-8編碼可以通過屏蔽位和移位操作快速讀寫。字符串比較時 strcmp()和wcscmp()的返回結果相同,因此使排序變得更加容易。字節FF和FE在UTF-8編碼中永遠不會出現,因此他們可以用來表明 UTF-16或UTF-32文本(見BOM) UTF-8 是字節順序無關的。它的字節順序在所有系統中都是一樣的,因此它實際上並不需要BOM。
UTF-8是ASCII的一個超集。因爲一個純ASCII字符串也是一個合法的UTF-8字符串,所以現存的ASCII文本不需要轉換。爲傳統的擴展ASCII字符集設計的軟件通常可以不經修改或很少修改就能與UTF-8一起使用;
使用標準的面向字節的排序例程對UTF-8排序將產生與基於Unicode代碼點排序相同的結果。(儘管這隻有有限的有用性,因爲在任何特定語言或文化下都不太可能有仍可接受的文字排列順序);
UTF-8和UTF-16都是可擴展標記語言文檔的標準編碼,所有其它編碼都必須通過顯式或文本聲明來指定;
任何面向字節的字符串搜索算法都可以用於UTF-8的數據(只要輸入僅由完整的UTF-8字符組成)。但是,對於包含字符記數的正則表達式或其它結構必須小心。
UTF-8字符串可以由一個簡單的算法可靠地識別出來。就是:一個字 符串在任何其它編碼中表現爲合法的UTF-8的可能性很低,並隨字符串長度增長而減小。舉例說,字符值C0,C1,F5至FF從來沒有出現。爲了更好的可 靠性,可以使用正則表達式來統計非法過長和替代值(可以查看W3 FAQ: Multilingual Forms上的驗證UTF-8字符串的正則表達式)。
3.2.3 UTF-8的缺點
A 不利於正則表達式檢索 , 正則表達式可以進行很多英文高級的模糊檢索。例如,[a-h]表示a到h間所有字母。同樣GBK編碼的中文也可以這樣利用正則表達式,比如在只知道一個字 的讀音而不知道怎麼寫的情況下,也可用正則表達式檢索,因爲GBK編碼是按讀音排序的。只是UTF-8不是按讀音排序的,所以會對正則表達式檢索造成不利 影響。但是這種使用方式並未考慮中文中的破音字,因此影響不大。Unicode是按部首排序的,因此在只知道一個字的部首而不知道如何發音的情況 下,UTF-8 可用正則表達式檢索而GBK不行。
B 你無法從UNICODE字符數判斷出UTF-8文本的字節數 , 因爲UTF-8是一種變長編碼它需要用2個字節編碼那些用擴展ASCII字符集只需1個字節的字符 ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集 8位字符的UTF-8編碼會被email網關過濾,因爲internet信息最初設計爲7爲ASCII碼。因此產生了UTF-7編碼。UTF-8 在它的表示中使用值100xxxxx的機率超過50%,而現存的實現如ISO 2022,4873,6429,和8859系統,會把它錯認爲是C1 控制碼。因此產生了UTF-7.5編碼。
一份寫得很差(並且與當前標準的版本不兼容)的UTF-8解析器可能會接受一些不同的僞UTF-8表示並將它們轉換到相同的Unicode輸出上。這爲設計用於處理八位表示的校驗例程提供了一種遺漏信息的方式。
C 佔據空間大, 與 其他 Unicode 編碼相比,特別是UTF-16,在 UTF-8 中 ASCII 字符佔用的空間只有一半,可是在一些字符的 UTF-8 編碼佔用的空間就要多出,特別是中文、日文和韓文(CJK)這樣的象形文字,所以具體因素因文檔而異,但不論哪種情況,差別都不可能很明顯。
3.3 UTF-8的編碼方式
UTF-8是UNICODE的一種變長度的編碼表達方式(一般 UNICODE爲雙字節[指UCS2]),UTF-8就是以8位爲單元對UCS進行編碼,而UTF-8不使用大尾序和小尾序的形式,每個使用UTF-8儲 存的字符,除了第一個字節外,其餘字節的頭兩個位元都是以"10"開始,使文字處理器能夠較快地找出每個字符的開始位置。
爲了與以前的ASCII碼相容(ASCII爲一個字節),因此 UTF-8 選擇了使用可變長度字節來儲存 Unicode,具體轉換關係如下表:
UCS-4 (UNICODE)編碼
UTF-8 字節流
U-00000000 – U-0000007F
0xxxxxxx
U-00000080 – U-000007FF
110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF
1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
(表3-2 Unicode和UTF-8之間的轉換關係表)
在ASCII碼的範圍,用一個字節表示,超出ASCII碼的範圍就用 字節表示,這就形成了我們上面看到的UTF-8的表示方法,這?的好處是當UNICODE文件中只有ASCII碼時,儲存的文件都爲一個字節,所以就是普 通的ASCII文件無異,讀取的時候也是如此,所以能與以前的ASCII文件相容。
    大於ASCII碼的,就會由上面的第一字節的前幾位表示該unicode字符的長度,比如110xxxxxx前三位的二進制表示告訴我們這是個 2BYTE的UNICODE字符;1110xxxx是個三位的UNICODE字符,依此類推;xxx 的位置由字符編碼數的二進制表示的位填入。越靠右的 x 具有越少的特殊意義。只用最短的那個足夠表達一個字符編碼數的多字節串。注意在多字節串中,第一個字節的開頭"1"的數目就是整個串中字節的數目。
ASCII字母繼續使用1字節儲存,重音文字、希臘字母或西裏爾字母等使用2字節來儲存,而常用的漢字就要使用3字節。輔助平面字符則使用4字節。
在UTF-8文件的開首,很多時都放置一個U+FEFF字符(UTF-8以EF,BB,BF代表),以顯示這個文字檔案是以UTF-8編碼。
 
4 UNICODE與UTF-8的轉換
4.1 UNICODE轉換爲UTF-8
UTF-8的特點是對不同範圍的字符使用不同長度的編碼。對於 0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節。從表3-2可以看出,4字節模板有21個 x,即可以容納21位二進制數字。Unicode的最大碼位0x10FFFF也只有21位。
    如:“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
    又如:Unicode編碼0x20C30在0x010000-0x10FFFF之間,使用4字節模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。將0x20C30寫成21位二進制數字(不足21位就在前面補0):0 0010 0000 1100 0011 0000,用這個比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
4.2 UTF-8轉換爲UNICODE
4.2.1 java中文亂碼出現的原因
亂碼指的是計算機系統不能顯示正確的字符,而顯示其他無意義的字符或空白,如一堆ASCII代碼。這樣所顯示出來的文本統稱爲亂碼。
亂碼是因爲“所使用的字符的源碼在本地計算機上使用了錯誤的顯示字庫 ”,或在本地計算機的字庫中找不到相應於源碼所指代的字符所致。不同國家和地區的文本字庫採用了相同的一段源碼,或是源文件中因爲文件受到破壞,致使計算 機默認提取的源碼錯誤,或是計算機沒有安裝相應字庫,都有可能產生亂碼。
java 採用unicode 編碼來處理字符。Java 程序無論是從向文件系統以字符流讀、寫文件,還是向URL連接寫HTML信息,或從URL連接讀取參數值,都會有字符編碼的轉換。下圖編碼——解碼的示意圖:
(圖4-1 編碼/解碼示意圖)
java亂碼產生的根源是由於編碼和解碼採用的不是同一種碼 (GBK、 UTF-8、iso8859-1、GB2312等),如將UNICODE編碼按照UTF-8進行了編碼,而解碼的時候卻用的是iso8859-1,此時在 java程序中便會出現與原UNICODE編碼不一致的情況,即出現亂碼。下面以字符串“中國”爲例,具體說明中文亂碼出現的原因。
    字符(String或char[])"中國" 經過java 編碼後的字節流(unicode 字節流)爲:4E 2D  56 FD ,如果你用new String("中國".getBytes("UTF-8"), "GB2312") 就會產生亂碼,如圖:
(圖4-2 亂碼出現原因示意圖)
因爲getBytes("UTF-8") 取得的是"中國" 經過UTF-8編碼後的字節流E4 B8 AD E5 9B BD(UTF-8字節流),而在用new String(bytes, "GB2312") 構造字符串時java 則將UTF-8字節流(E4 B8 AD E5 9B BD)當作是unicode 字節流(因爲java是採用unicode 來處理字符的,所以它把字節流統統當作是unicode 字節流),因此它把E4 B8 AD E5 9B BD也看成是unicode字節流。而unicode 字節流(E4 B8 AD E5 9B BD)經過GB2312 編碼後就成了“涓??”。於是,亂碼產生了。
解決亂碼的一般方法是進行正確的解碼操作,如編碼使用的是utf-8的方式,那麼解碼是也須用utf-8進行解碼。
正確的解碼流程應該是:
(圖4-3 UTF-8正確的解碼示意圖)
4.2.2 將UTF-8轉換爲UNICODE
    圖3-3勾畫了將UTF-8轉換爲UNICODE的基本流程,根據表3-2(Unicode和UTF-8之間的轉換關係)可以看出,將UTF-8轉換爲 UNICODE的過程,實際上是將按UTF-8編碼規則填充至UNICODE的編碼提取出來的過程,即將8位的字節“xxxx xxxx”提取出來的過程,如某字符以“1110xxxx 10xxxxxx 10xxxxxx”的編碼方式進行UTF-8編碼,還原後UNICODE編碼爲:“xxxx xxxx”。
    例如字符“中”,二進制序列(雙字節)爲:0100 1110 0010 1101,進行UTF-8編碼後的字節二進制序列(三字節)爲:1110 0100  10 111000  10 101101,進行UTF-8轉碼即去掉第一個字節的“1110”,第二個字節的“10”,第三個字節的“10”,然後再將剩餘的二進制序列組合成一個雙 字節的二進制序列,即還原爲:0100 1110 0010 1101,如此則完成轉碼。
下面的代碼是將以“1110xxxx 10xxxxxx 10xxxxxx”的編碼方式進行UTF-8編碼,還原爲UNICODE編碼的操作:
/**
* 將utf-8編碼轉化爲unicode編碼
 * @param aByte byte[] -原utf-8編碼字節數組
 * return sByte byte[] -轉化後的unicode編碼字節數組
 */
public static String changeUtf8ToUnicode(byte[] aByte) {
   int sLength = aByte.length; //原字節數組長度
   //存儲轉化爲unicode編碼後的StringBuffer字符串
   StringBuffer sUnicodeStringBuffer = new StringBuffer();
   char sChar; //用於臨時存放每個從utf-8中解析出來的unicode編碼
   //以下操作是判斷字節是否以"1110 xxxx 10xxxxxx 10xxxxxx"的形式出現
   for (int i = 0; i < sLength; i++) { //循環每一個字節
       if (i + 2 < sLength) {
          /**
           * aByte[i] & 0xF0 == 0xE0       ---> 判斷當前字節是否以“1110”的形式開始;
           * aByte[i + 1] & 0xC0 == 0x80   ---> 判斷下一個字節是否以“10”的形式開始;
           * aByte[i + 2] & 0xC0 == 0x80   ---> 判斷下下一個字節是否以“10”的形式開始。
           * 假如條件都滿足,則表示此斷字節進行了utf-8編碼,則將對其進行解碼操作(即轉
* 化爲unicode編碼)
           */
          if ((aByte[i] & 0xF0) == 0xE0 && (aByte[i + 1] & 0xC0) == 0x80 &&
              (aByte[i + 2] & 0xC0) == 0x80) {
              /**
               * 將當前字節 1110 xxxx 轉化爲 xxxx 000000 000000 的形式,具體步驟爲:
               * 1110 xxxx << 12 = xxxx 000000 000000
               * 1110 0100 << 12 = 0100 000000 000000
               */
              sChar = (char) (aByte[i] << 12);
              /**
               * 將 前兩個字節 轉化爲 xxxx xxxxxx 000000 的形式,具體步驟爲:
               * 10 xxxxxx & 0x003F = 0000 000000 xxxxxx
               * 10 111000 & 0x003F = 0000 000000 111000
               *
               * 0000 000000 xxxxxx << 6 = 0000 xxxxxx 000000
              * 0000 000000 111000 << 6 = 0000 111000 000000
               *
               * xxxx 000000 000000 | 0000 xxxxxx 000000 = xxxx xxxxxx 000000
               * 0100 000000 000000 | 0000 111000 000000 = 0100 111000 000000
               */
              sChar = (char) ((((aByte[i + 1] & 0x003F) << 6) | sChar));
              /**
               * 將此三個字節轉化爲 xxxx xxxxxx xxxxxx 的形式,具體步驟爲:
               * 10 xxxxxx & 0x003F = 0000 0000 00 xxxxxx
               * 10 101101 & 0x003F = 0000 0000 00 101101
               *
               * xxxx xxxxxx 000000 | 0000 000000 xxxxxx = xxxx xxxxxx xxxxxx
               * 0100 111000 000000 | 0000 000000 101101 = 0100 111000 101101
               */
              sChar = (char) ((aByte[i + 2] & 0x003F) | sChar);
               i = i + 2;
               sUnicodeStringBuffer.append(sChar);
           } else {
               sUnicodeStringBuffer.append((char) aByte[i]);
           }
       }
     }
   return sUnicodeStringBuffer.toString();
}
(代碼4-1 將UTF-8的一種編碼方式轉換爲UNICODE)
    該代碼是僅轉換以“1110xxxx 10xxxxxx 10xxxxxx”形式進行UTF-8編碼過的字節流,而要徹底的將UTF-8編碼轉換爲UNICODE編碼,則需要對錶3-2(Unicode和 UTF-8之間的轉換關係)中的六種編碼方式進行處理。在java中,Unicode代碼點的作用範圍是U+0000至U+10FFFF之間的字符值,所 以表3-2中的前四種編碼方式在java中有效,前三種編碼方式(BOM)可以直接將編碼前的兩個字節(1個單位的char)提取出來,組成一個 char,以達到解碼的目的,具體實現代碼爲:
/**
 * 將UTF-8編碼解碼
 * @param aByte byte[]
 * @return String
 */
 public static String changeUtf8ToUnicode(byte[] aByte) {
     StringBuffer sUnicodeStringBuffer = new StringBuffer();
     int sLength = aByte.length;
     int sInt_1, sInt_2, sInt_3, sInt_4, sInt_5, sInt_6;
     for (int i = 0; i < sLength; i++) {
         sInt_1 = (int) aByte[i] & 0xff;
         switch (sInt_1 >> 4) {
         case 0:
         case 1:
         case 2:
         case 3:
         case 4:
         case 5:
         case 6:
         case 7:
              /* 0xxxxxxx*/
             sUnicodeStringBuffer.append((char) aByte[i]);
             break;
         case 12:
         case 13:
              /* 110x xxxx   10xx xxxx*/
             if (i + 1 < sLength) {
                 sInt_2 = (char) aByte[i + 1];
                 if ((sInt_2 & 0xC0) == 0x80) {
                     sUnicodeStringBuffer.append((char)(((sInt_1 & 0x1F) << 6)
 | (sInt_2 & 0x3F)));
                     i ++;
                 }
             }
             break;
         case 14:
             /* 1110 xxxx 10xx xxxx 10xx xxxx */
             if (i + 2 < sLength) {
                 sInt_2 = (int) aByte[i + 1];
                 sInt_3 = (int) aByte[i + 2];
                 if (((sInt_2 & 0xC0) == 0x80) || ((sInt_3 & 0xC0) == 0x80)) {
                     sUnicodeStringBuffer.append((char)(((sInt_1 & 0x0F) << 12) | ((sInt_2 & 0x3F) << 6) | ((sInt_3 & 0x3F) << 0)));
                     i = i + 2;
                 }
             }
             break;
         }
        }
        return sUnicodeStringBuffer.toString();
    }
(代碼4-2 將UTF-8的前三種編碼方式[BOM]還原爲UNICODE)
前三種編碼方式,即從U-00000000 - U-0000FFFF區域,包含了99.9%的日常使用字符,一般進行UTF-8與UNICODE的轉換時,僅考慮前三中編碼方式便可。
第四個編碼方式(U-00010000 – U-001FFFFF)則需要使用增補字符進行處理。下面將具體講解java平臺中的增補字符。
4.2.3 Java平臺中的增補字符
增補字符是Unicode標準中代碼點超出U+FFFF的字符,而java中的增補字符則是指Unicode代碼點的作用範圍在U+FFFF至U+10FFFF之間的字符值。
4.2.3.1 Java平臺中增補字符出現背景
Unicode最初設計是作爲一種固定寬度的16位字符編碼。在 Java編程語言中,基本數據類型char初衷是通過提供一種簡單的、能夠包含任何字符的數據類型來充分利用這種設計的優點。不過,現在看來,16位編碼 的所有65,536 個字符並不能完全表示全世界所有正在使用或曾經使用的字符。於是,Unicode 標準已擴展到包含多達 1,112,064 個字符。那些超出原來的 16 位限制的字符被稱作增補字符。Unicode 標準 2.0 版是第一個包含啓用增補字符設計的版本,但是,直到 3.1 版才收入第一批增補字符集。由於 J2SE 的 5.0 版必須支持 Unicode 標準 4.0 版,因此它必須支持增補字符。
    對增補字符的支持也可能會成爲東亞市場的一個普遍商業要求。政府應用程序會需要這些增補字符,以正確表示一些包含罕見中文字符的姓名。出版應用程序可能會 需要這些增補字符,以表示所有的古代字符和變體字符。中國政府要求支持GB18030(一種對整個 Unicode字符集進行編碼的字符編碼標準),因此,如果是Unicode 3.1版或更新版本,則將包括增補字符。臺灣標準 CNS-11643包含的許多字符在 Unicode 3.1 中列爲增補字符。香港政府定義了一種針對粵語的字符集,其中的一些字符是Unicode中的增補字符。最後,日本的一些供應商正計劃利用增補字符空間中大 量的專用空間收入50,000多個日文漢字字符變體,以便從其專有系統遷移至基於Java平臺的解決方案。
    因此,Java平臺不僅需要支持增補字符,而且必須使應用程序能夠方便地做到這一點。由於增補字符打破了Java編程語言的基礎設計構想,而且可能要求對 編程模型進行根本性的修改,因此,Java Community Process召集了一個專家組,以期找到一個適當的解決方案。該小組被稱爲JSR-204專家組,使用 Unicode增補字符支持的Java 技術規範請求的編號。從技術上來說,該專家組的決定僅適用於J2SE平臺,但是由於 Java 2平臺企業版 (J2EE)處於 J2SE 平臺的最上層,因此它可以直接受益,我們期望Java 2平臺袖珍版 (J2ME)的配置也採用相同的設計方法。
4.2.3.2 Java平臺中增補字符的設計方法
JSR-204 專家組必須作出的主要決定是如何在Java API中表示增補字符,包括單個字符和所有形式的字符序列。專家組考慮並排除了多種方法:
* 重新定義基本類型char,使其具有32位,這樣也會使所有形式的 char序列成爲UTF-32序列;
* 在現有的16位類型char的基礎上,爲字符引入一種新的32位基本類型(例如,char32)。所有形式的Char序列均基於 UTF-16;
* 在現有的16位類型char的基礎上,爲字符引入一種新的32位基本類型(例如,char32)。String和StringBuffer 接受並行API,並將它們解釋爲UTF-16序列或UTF-32序列;其他char序列繼續基於UTF-16;
* 使用int表示增補的代碼點。String和StringBuffer接受並行API,並將它們解釋爲UTF-16序列或UTF-32序列;其他char序列繼續基於UTF-16;
* 使用代理char對,表示增補代碼點。所有形式的char序列基於UTF-16;
* 引入一種封裝字符的類。String和StringBuffer接受新的API,並將它們解釋爲此類字符的序列;
* 使用一個CharSequence實例和一個索引的組合表示代碼點。
(表4-1 專家組考慮的增補字符設計方法)
在這些方法中,一些在早期就被排除了。例如,重新定義基本類型 char,使其具有32位,這對於全新的平臺可能會非常有吸引力,但是,對於J2SE來說,它會與現有的Java虛擬機、序列化和其他接口不兼容,更不用 說基於 UTF-32的字符串要使用兩倍於基於UTF-16的字符串的內存了。添加一種新類型的 char32可能會簡單一些,但是仍然會出現虛擬機和序列化方面的問題。而且,語言更改通常需要比API更改有更長的提前期,因此,前面兩種方法會對增補 字符支持帶來無法接受的延遲。爲了在餘下的方法中篩選出最優方案,實現小組使用四種不同的方法,在大量進行低層字符處理的代碼 (java.util.regex包)中實現了對增補字符支持,並對這四種方法的難易程度和運行表現進行了比較。最終,專家組確定了一種分層的方法:
* 使用基本類型int在低層API中表示代碼點,例如Character類的靜態方法。
* 將所有形式的char序列均解釋爲UTF-16序列,並促進其在更高層級API中的使用。
* 提供API,以方便在各種char和基於代碼點的表示法之間的轉換。
(表4-2 專家組最終確定的增補字符設計方法)
在需要時,此方法既能夠提供一種概念簡明且高效的單個字符表示法,又能夠充分利用通過改進可支持增補字符的現有API。同時,還能夠促進字符序列在單個字符上的應用,這一點一般對於國際化的軟件很有好處。
4.2.3.2 開放的增補字符——基於代碼點的API
    新增的低層API分爲兩大類:用於各種char和基於代碼點的表示法之間轉換的方法和用於分析和映射代碼點的方法。
    最基本的轉換方法是Character.toCodePoint(char high, char low)(用於將兩個UTF-16代碼單元轉換爲一個代碼點)和 Character.toChars(int codePoint)(用於將指定的代碼點轉換爲一個或兩個 UTF-16 代碼單元,然後封裝到一個char[]內。不過,由於大多數情況下文本以字符序列的形式出現,因此,另外提供codePointAt和 codePointBefore方法,用於將代碼點從各種字符序列表示法中提取出來:Character.codePointAt(char[] a, int index)和String.codePointBefore(int index)是兩種典型的例子。在將代碼點插入字符序列時,大多數情況下均有一些針對StringBuffer和StringBuilder類的 appendCodePoint(int codePoint)方法,以及一個用於提取表示代碼點的int[]的String構建器。
    幾種用於分析代碼單元和代碼點的方法有助於轉換過程:Character類中的 isHighSurrogate和isLowSurrogate方法可以識別用於表示增補字符的char 值;charCount(int codePoint)方法可以確定是否需要將某個代碼點轉換爲一個或兩個char。
    但是,大多數基於代碼點的方法均能夠對所有Unicode字符實現基於char的舊方法對BMP字符所實現的功能。以下是一些典型例子:
* Character.isLetter(int codePoint)可根據Unicode標準識別字母;
* Character.isJavaIdentifierStart(int codePoint)可根據Java語言規範確定代碼點是否可以啓動標識符;
* Character.UnicodeBlock.of(int codePoint)可搜索代碼點所屬的Unicode字符子集;
* Character.toUpperCase(int codePoint)可將給定的代碼點轉換爲其大寫等值字符。儘管此方法能夠支持增補字符,但是它仍然不能解決根本的問題,即在某些情況下,逐個字符的轉 換無法正確完成。例如,德文字符“ß”應該轉換爲“SS”,這需要使用String.toUpperCase方法;
(表4-3 代碼點方法對BOM字符的處理實例)
注意:大多數接受代碼點的方法並不檢查給定的int值是否處於有效的 Unicode代碼點範圍之內(如上所述,只有0x0至0x10FFFF之間的範圍是有效的)。在大多數情況下,該值是以確保其有效的方法產生的,在這些 低層API中反覆檢查其有效性可能會對系統性能造成負面的影響。在無法確保有效性的情況下,應用程序必須使用 Character.isValidCodePoint方法確保代碼點有效。大多數方法對於無效的代碼點採取的行爲沒有特別加以指定,不同的實現可能會有 所不同。
    API包含許多簡便的方法,這些方法可使用其他低層的API實現,但是專家組覺得,這些方法很常用,將它們添加到J2SE平臺上很有意義。不過,專家組也 排除了一些建議的簡便方法,這給我們提供了一次展示自己實現此類方法能力的機會。例如,專家組經過討論,排除了一種針對String類的新構建器(該構建 器可以創建一個保持單個代碼點的String)。以下是使應用程序使用現有的API提供功能的一種簡便方法:
/**
 * 創建僅含有指定代碼點的新 String。
 */
String newString(int codePoint) {
    return new String(Character.toChars(codePoint));
}
(代碼4-3 使應用程序使用現有的API提供功能的一種簡便方法)
您會注意到,在這個簡單的實現中,toChars方法始終創建一箇中間數列,該數列僅使用一次即立即丟棄。如果該方法在您的性能評估中出現,您可能會希望將其優化爲針對最爲普通的情況,即該代碼點爲BMP字符:
/**
 * 創建僅含有指定代碼點的新String。
 * 針對BMP字符優化的版本。
 */
String newString(int codePoint) {
    if (Character.charCount(codePoint) == 1) {
        return String.valueOf((char) codePoint);
    } else {
        return new String(Character.toChars(codePoint));
    }
}
(代碼4-4 使應用程序使用現有的API提供功能的最爲普通的情況)
或者,如果您需要創建許多個這樣的string,則可能希望編寫一個重複使用toChars方法所使用的數列的通用版本:
/**
 * 創建每一個均含有一個指定
 * 代碼點的新 String。
 * 針對 BMP 字符優化的版本。
 */
String[] newStrings(int[] codePoints) {
    String[] result = new String[codePoints.length];
    char[] codeUnits = new char[2];
    for (int i = 0; i < codePoints.length; i++) {
         int count = Character.toChars(codePoints[i], codeUnits, 0);
         result[i] = new String(codeUnits, 0, count);
    }
    return result;
}
(代碼4-5 toChars方法所使用的數列的通用版本)
不過,最終您可能會發現,您需要的是一個完全不同的解決方案。新的構建器String(intcodePoint)實際上建議作爲String.valueOf(char)的一個基於代碼點的備選方案。在很多情況下,此方法用於消息生成的環境,例如:
System.out.println("Character " + String.valueOf(char) + " is invalid.");
(代碼4-6 消息生成的環境)
新的格式化API支持增補文字,提供一種更加簡單的備選方案:
System.out.printf("Character %c is invalid.%n", codePoint);
(代碼4-7 增補文字的備選方案)
使用此高層API不僅簡捷,而它有很多特殊的優點:它可以避免串聯(串聯會使消息很難本地化),並將需要移進資源包(resourcebundle)的字符串數量從兩個減少到一個。
    在Java編程語言源文件中,如果使用可以直接表示增補字符的字符編碼,則使用增補字符最爲方便。UTF-8是最佳的選擇。在所使用的字符編碼無法直接表 示字符的情況下,Java編程語言提供一種Unicode轉義符語法。此語法沒有經過增強,無法直接表示增補字符。而是使用兩個連續的Unicode轉義 符將其表示爲UTF-16字符表示法中的兩個編碼單元。例如,字符U+20000寫作“/uD840/uDC00”。最好是寫入支持所需增補字符的編碼, 然後使用一種工具(如native2ascii)將其轉換爲轉義序列。
    遺憾的是,由於其編碼問題,屬性文件仍侷限於ISO8859-1(除非您的應用程序使用新的XML格式)。這意味着您始終必須對增補字符使用轉義序列,而且可能要使用不同的編碼進行編寫,然後使用諸如native2ascii的工具進行轉換。
4.2.3.3 經修訂的UTF-8
    Java平臺對經修訂的UTF-8已經很熟悉,但是,問題是應用程序開發人員在可能包含增補字符的文本和UTF-8之間進行轉換時需要更加留神。需要特別 注意的是,某些J2SE接口使用的編碼與UTF-8相似但與其並不兼容。以前,此編碼有時被稱爲“JavamodifiedUTF-8”(經Java修訂 的UTF-8)或(錯誤地)直接稱爲“UTF-8”。對於J2SE5.0,其說明文檔正在更新,此編碼將統稱爲“modifiedUTF-8”(經修訂的 UTF-8)。
    經修訂的UTF-8和標準UTF-8之間之所以不兼容,其原因有兩點。其一,經修訂的UTF-8將字符U+0000表示爲雙字節序列0xC00x80,而 標準UTF-8使用單字節值0x0。其二,經修訂的UTF-8通過對其UTF-16表示法的兩個代理代碼單元單獨進行編碼表示增補字符。每個代理代碼單元 由三個字節來表示,共有六個字節。而標準UTF-8使用單個四字節序列表示整個字符。
    Java虛擬機及其附帶的接口(如Java本機接口、多種工具接口或Java類文件)在java.io.DataInput和DataOutput接口和 類中使用經修訂的UTF-8實現或使用這些接口和類,並進行序列化。Java本機接口提供與經修訂的UTF-8之間進行轉換的例程。而標準UTF-8由 String類、java.io.InputStreamReader和OutputStreamWriter類、java.nio.charset設施 (facility)以及許多其上層的API提供支持。
    由於經修訂的UTF-8與標準的UTF-8不兼容,因此切勿同時使用這兩種版本的編碼。經修訂的UTF-8只能與上述的Java接口配合使用。在任何其他 情況下,尤其對於可能來自非基於Java平臺的軟件的或可能通過其編譯的數據流,必須使用標準的UTF-8。需要使用標準的UTF-8時,則不能使用 Java本機接口例程與經修訂的UTF-8進行轉換。
4.2.3.4 在應用程序內支持增補字符
    對於僅以各種形式char序列([char[]、java.lang.CharSequence實現、 java.text.CharacterIterator實現)處理文本和僅使用接受和退回序列(如char序列)的JavaAPI的應用程序,可能根本 不需要進行任何更改。Java平臺API的實現應該能夠處理增補字符。
    對於本身解釋單個字符、將單個字符傳送給Java平臺API或調用能夠返回單個字符的方法的應用程序,則需要考慮這些字符的有效值。在很多情況下,往往不 要求支持增補字符。例如,如果某應用程序搜索char序列中的HTML標記,並逐一檢查每個char,它會知道這些標記僅使用BasicLatin字符子 集中的字符。如果所搜索的文本含有增補字符,則這些字符不會與標記字符混淆,因爲UTF-16使用代碼單元表示增補字符,而代碼單元的值不會用於BMP字 符。
    只有在某應用程序本身解釋單個字符、將單個字符傳送給Java平臺API或調用能夠返回單個字符的方法且這些字符可能爲增補字符時,才必須更改該應用程 序。在提供使用char序列的並行API時,最好轉而使用此類API。在其他情況下,有必要使用新的API在char和基於代碼點的表示法之間進行轉換, 並調用基於代碼點的API。當然,如果您發現在J2SE5.0中有更新、更方便的API,使您能夠支持增補字符並同時簡化代碼(如上格式化範例中所述), 則沒有必要這樣做。
    您可能會猶豫,是將所有文本轉換爲代碼點表示法(即int[])然後在該表示法中處理,還是在大多數情況下仍採用char序列,僅在需要時轉換爲代碼點, 兩者之間孰優孰劣很難確定。當然,總體來說,Java平臺API相對於char序列肯定具有一定的優勢,而且採用Java平臺API可以節省內存空間。
    對於需要與UTF-8之間進行轉換的應用程序,還需要認真考慮是需要標準的UTF-8還是經修訂的UTF-8,並針對每種UTF-8採用適當的Java平臺。“經修訂的UTF-8”部分介紹進行正確選擇所需的信息。
 
參考文獻:
[1] 維基百科.UTF-8[EB/OL]. http://zh.wikipedia.org/w/index.php?title=UTF-8&vari
ant=zh-cn,2007-06-04.
[2] 維基百科.UNICODE[EB/OL]. http://zh.wikipedia.org/w/index.php?title=Unicode&
variant=zh-cn,2007-07-08.
[3] 百度.UNICODE百科[EB/OL]. http://baike.baidu.com/view/40801.htm,2005-07-18.
[4] Norbert Lindenberg.java中的增補字符[EB/OL]. http://gceclub.sun.com.cn/devel
oper/technicalArticles/Intl/Supplementary/index_zh_CN.html,2004-05-16.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章