我曾經(jīng)寫過一篇《談?wù)刄nicode編碼,簡要解釋UCS、UTF、BMP、BOM等名詞》(以 下簡稱《談?wù)刄nicode編碼》),在網(wǎng)上流傳較廣,我也收到不少朋友的反饋。本文探討《談?wù)刄nicode編碼》中未介紹或介紹較少的代碼頁、 Surrogates等問題,補充一些Unicode資料,順帶介紹一下我最近編寫的一個Unicode工具:UniToy。本文雖然是前文的補充,但在 寫作上盡量做到獨立成篇。 標題中的“淺談”是對自己的要求,我希望文字能盡量淺顯易懂。但本文還是假設(shè)讀者知道字節(jié)、16進制,了解《談?wù)刄nicode編碼》中介紹過的字節(jié)序和Unicode的基本概念。 0 UniToyUniToy是我編寫的一個小工具。通過UniToy,我們可以全方位、多角度地查看Unicode,了解Unicode和語言、代碼頁的關(guān)系,完成一些文字編碼的相關(guān)工作。本文的一些內(nèi)容是通過UniToy演示的。大家可以從我的網(wǎng)站(www.)下載UniToy的演示版本。1 文字的顯示1.1 發(fā)生了什么?我們首先以Windows為例來看看文字顯示過程中發(fā)生了什么。用記事本打開一個文本文件,可以看到文件包含的文字: 如果我們用UltraEdit或Hex Workshop查看這個文件的16進制數(shù)據(jù),可以看到: 我們看到:文件“例子GBK.txt”有10個字節(jié),依次是“D7 D6 B7 FB BA CD B1 E0 C2 EB”,這就是記事本從文件中讀到的內(nèi)容。記事本是用來打開文本文件的,所以它會調(diào)用Windows的文本顯示函數(shù)將讀到的數(shù)據(jù)作為文本顯示。 Windows首先將文本數(shù)據(jù)轉(zhuǎn)換到它內(nèi)部使用的編碼格式:Unicode,然后按照文本的Unicode去字體文件中查找字體圖像,最后將圖像顯示到窗 口上??偨Y(jié)一下前面的分析,文字的顯示應(yīng)該是這樣的:
如果上述3個步驟中任何一步發(fā)生了錯誤,文字就不能被正確顯示,例如:
在Unicode被廣泛使用前,有多少種語言、文字,就可能有多少種文字編碼方案。一種文字也可能有多種編碼方案。那么我們怎么確定文本數(shù)據(jù)采用了什么編碼? 1.2 采用了哪種編碼?按照慣例,文本文件中的數(shù)據(jù)都是文本編碼,那么它怎么表明自己的編碼格式?在記事本的“打開”對話框上: 我們可以看到記事本支持4種編碼格式:ANSI、Unicode、Unicode big endian、UTF-8。如果讀者看過《談?wù)刄nicode編碼》,對Unicode、Unicode big endian、UTF-8應(yīng)該不會陌生,其實它們更準確的名稱應(yīng)該是UTF-16LE(Little Endian)、UTF-16BE(Big Endian)和UTF-8,它們是基于Unicode的不同編碼方案。 在《談?wù)刄nicode編碼》中介紹過,Windows通過在文本文件開頭增加一些特殊字節(jié)(BOM)來區(qū)分上述3種編碼,并將沒有BOM的文本數(shù)據(jù)按照ANSI代碼頁處理。那么什么是代碼頁,什么是ANSI代碼頁? 2 代碼頁和字符集2.1 Windows的代碼頁2.1.1 代碼頁代碼頁(Code Page)是個古老的專業(yè)術(shù)語,據(jù)說是IBM公司首先使用的。代碼頁和字符集的含義基本相同,代碼頁規(guī)定了適用于特定地區(qū)的字符集合,和這些字符的編碼??梢詫⒋a頁理解為字符和字節(jié)數(shù)據(jù)的映射表。 Windows為自己支持的代碼頁都編了一個號碼。例如代碼頁936就是簡體中文 GBK,代碼頁950就是繁體中文 Big5。代碼頁的概念比較簡單,就是一個字符編碼方案。但要說清楚Windows的ANSI代碼頁,就要從Windows的區(qū)域(Locale)說起 了。 2.1.2 區(qū)域和ANSI代碼頁微軟為了適應(yīng)世界上不同地區(qū)用戶的文化背景和生活習慣,在Windows中設(shè)計了區(qū)域(Locale)設(shè)置的功能。 Local是指特定于某個國家或地區(qū)的一組設(shè)定,包括代碼頁,數(shù)字、貨幣、時間和日期的格式等。在Windows內(nèi)部,其實有兩個Locale設(shè)置:系統(tǒng) Locale和用戶Locale。系統(tǒng)Locale決定代碼頁,用戶Locale決定數(shù)字、貨幣、時間和日期的格式。我們可以在控制面板的“區(qū)域和語言選 項”中設(shè)置系統(tǒng)Locale和用戶Locale: 每個Locale都有一個對應(yīng)的代碼頁。Locale和代碼頁的對應(yīng)關(guān)系,大家可以參閱我的另一篇文章《談?wù)刉indows程序中的字符編碼》的附錄1。系統(tǒng)Locale對應(yīng)的代碼頁被作為Windows的默認代碼頁。在沒有文本編碼信息時,Windows按照默認代碼頁的編碼方案解釋文本數(shù)據(jù)。這個默認代碼頁通常被稱作ANSI代碼頁(ACP)。 ANSI代碼頁還有一層意思,就是微軟自己定義的代碼頁。在歷史上,IBM的個人計算機和微軟公司的操作系統(tǒng)曾經(jīng)是 PC的標準配置。微軟公司將IBM公司定義的代碼頁稱作OEM代碼頁,在IBM公司的代碼頁基礎(chǔ)上作了些增補后,作為自己的代碼頁,并冠以ANSI的字 樣。我們在“區(qū)域和語言選項”高級頁面的代碼頁轉(zhuǎn)換表中看到的包含ANSI字樣的代碼頁都是微軟自己定義的代碼頁。例如:
在UniToy中,我們可以按照代碼頁編碼順序查看這些代碼頁的字符和編碼:
我們不能直接設(shè)置ANSI代碼頁,只能通過選擇系統(tǒng)Locale,間接改變當前的ANSI代碼頁。微軟定義的Locale只使用自己定義的代碼頁。所以,我們雖然可以通過“區(qū)域和語言選項”中的代碼頁轉(zhuǎn)換表安裝很多代碼頁,但只能將微軟的代碼頁作為系統(tǒng)默認代碼頁。 2.1.3 代碼頁轉(zhuǎn)換表在Windows 2000以后,Windows統(tǒng)一采用UTF-16作為內(nèi)部字符編碼。現(xiàn)在,安裝一個代碼頁就是安裝一張代碼頁轉(zhuǎn)換表。通過代碼頁轉(zhuǎn)換表,Windows 既可以將代碼頁的編碼轉(zhuǎn)換到UTF-16,也可以將UTF-16轉(zhuǎn)換到代碼頁的編碼。代碼頁轉(zhuǎn)換表的具體實現(xiàn)可以是一個以nls為后綴的數(shù)據(jù)文件,也可以 是一個提供轉(zhuǎn)換函數(shù)的動態(tài)鏈接庫。有的代碼頁是不需要安裝的。例如:Windows將UTF-7和UTF-8分別作為代碼頁65000和代碼頁 65001。UTF-7、UTF-8和UTF-16都是基于Unicode的編碼方案。它們之間可以通過簡單的算法直接轉(zhuǎn)換,不需要安裝代碼頁轉(zhuǎn)換表。 在安裝過一個代碼頁后,Windows就知道怎樣將該代碼頁的文本轉(zhuǎn)換到Unicode文本,也知道怎樣將 Unicode文本轉(zhuǎn)換成該代碼頁的文本。例如:UniToy有導(dǎo)入和導(dǎo)出功能。所謂導(dǎo)入功能就是將任一代碼頁的文本文件轉(zhuǎn)換到Unicode文本;導(dǎo)出 功能就是將Unicode文本轉(zhuǎn)換到任一指定的代碼頁。這里所說的代碼頁就是指系統(tǒng)已安裝的代碼頁: 其實,如果全世界人民在計算機剛發(fā)明時就統(tǒng)一采用Unicode作為字符編碼,那么代碼頁就沒有存在的必要了??上? 在Unicode被發(fā)明前,世界各國人民都發(fā)明并使用了各種字符編碼方案。所以,Windows必須通過代碼頁支持已經(jīng)被廣泛使用的字符編碼。從這種意義 看,代碼頁主要是為了兼容現(xiàn)有的數(shù)據(jù)、程序和習慣而存在的。 2.1.4 SBCS、DBCS和MBCSSBCS、DBCS和MBCS分別是單字節(jié)字符集、雙字節(jié)字符集和多字節(jié)字符集的縮寫。SBCS、DBCS和 MBCS的最大編碼長度分別是1字節(jié)、兩字節(jié)和大于兩字節(jié)(例如4或5字節(jié))。例如:代碼頁1252 (ANSI-拉丁文 I)是單字節(jié)字符集;代碼頁936 (ANSI/OEM-簡體中文 GBK)是雙字節(jié)字符集;代碼頁54936 (GB18030 簡體中文)是多字節(jié)字符集。 單字節(jié)字符集中的字符都用一個字節(jié)表示。顯然,SBCS最多只能容納256個字符。 雙字節(jié)字符集的字符用一個或兩個字節(jié)表示。那么我們從文本數(shù)據(jù)中讀到一個字節(jié)時,怎么判斷它是單字節(jié)字符,還是雙字 節(jié)字符的首字符?答案是通過字節(jié)所處范圍來判斷。例如:在GBK編碼中,單字節(jié)字符的范圍是0x00-0x80,雙字節(jié)字符首字節(jié)的范圍是0x81到 0xFE。我們順序讀取字節(jié)數(shù)據(jù),如果讀到的字節(jié)在0x81到0xFE內(nèi),那么這個字節(jié)就是雙字節(jié)字符的首字節(jié)。GBK定義雙字節(jié)字符的尾字節(jié)范圍是 0x40到0x7E和0x80到0xFE。 GB18030是多字節(jié)字符集,它的字符可以用一個、兩個或四個字節(jié)表示。這時我們又如何判斷一個字節(jié)是屬于單字節(jié) 字符,雙字節(jié)字符,還是四字節(jié)字符?GB18030與GBK是兼容的,它利用了GBK雙字節(jié)字符尾字節(jié)的未使用碼位。GB18030的四字節(jié)字符的第一字 節(jié)的范圍也是0x81到0xFE,第二字節(jié)的范圍是0x30-0x39。通過第二字節(jié)所處范圍就可以區(qū)分雙字節(jié)字符和四字節(jié)字符。GB18030定義四字 節(jié)字符的第三字節(jié)范圍是0x81到0xFE,第四字節(jié)范圍是0x30-0x39。 2.2 代碼頁實例2.2.1 實例一:GB18030代碼頁1.1節(jié)的“錯誤2”中演示了一個全被顯示成‘?‘的文件。這個文件的數(shù)據(jù)是:
其實,這是一個包含了6個四字節(jié)字符的GB18030編碼的文件。記事本按照GBK顯示這些數(shù)據(jù),而GB18030 的四字節(jié)字符編碼在GBK中是未定義的。Windows根據(jù)首字節(jié)范圍判斷出12個雙字節(jié)字符,然后因為找不到匹配的轉(zhuǎn)換而將其映射到默認字符‘?‘。使 用UniToy按照GB18030代碼頁導(dǎo)入這個文件,就可以看到:
這個GB18030編碼的文件是用UniToy創(chuàng)建的,編輯Unicode文本,然后導(dǎo)出到GB18030編碼格式。 2.2.2 實例二:GBK和Big5的轉(zhuǎn)換綜合使用UniToy的導(dǎo)入、導(dǎo)出功能就可以在任意兩個代碼頁之間轉(zhuǎn)換文本。其實,由于各代碼頁支持的字符范圍不同,我們一般不會直接在代碼頁間轉(zhuǎn)換文本。例如將以下GBK編碼的文本: 直接轉(zhuǎn)換到Big5編碼,就會看到: 變成‘?‘的字符都是Big5編碼不支持的簡化字。在從Unicode轉(zhuǎn)換到Big5編碼時,由于Big5編碼不支持這些字符,Windows就用默認字符‘?‘代替。在UniToy中,我們可以先將簡體字轉(zhuǎn)換到繁體字,然后再導(dǎo)出到Big5編碼,就可以正常顯示: 同理,將Big5編碼的文本轉(zhuǎn)換到GBK編碼的步驟應(yīng)該是:
2.3 互聯(lián)網(wǎng)的字符集2.3.1 字符集互聯(lián)網(wǎng)上的信息繽紛多彩,但文本依然是最重要的信息載體。html文件通過標記表明自己使用的字符集。例如: <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 或者: <meta http-equiv="charset" content="iso-8859-1"> 那么我們可以使用哪些字符集(charset)呢?在IETF(互聯(lián)網(wǎng)工程任務(wù)組)的網(wǎng)頁上維護著一份可以在互聯(lián)網(wǎng)上使用的字符集的清單:CHARACTER SETS。如果有新的字符集被登記,IETF會更新這份文檔。 簡單瀏覽一下,2006年12月7日的版本列出了253個字符集。其中也包括微軟的CP1250 ~ CP1258,在這里它們不會被稱作什么ANSI代碼頁,而是被簡單地稱作windows-1250、windows-1251等。其實在Unicode 被廣泛使用前,除了中日韓等大字符集,世界上,特別是西方使用最廣泛的字符集應(yīng)該是ISO 8859系列字符集。 2.3.2 ISO 8859系列字符集ISO 8859系列字符集是歐洲計算機制造商協(xié)會(ECMA)在上世紀80年代中期設(shè)計,并被國際標準化(ISO)組織采納為國際標準。ISO 8859系列字符集目前有15個字符集,包括:
其中缺少的編號12據(jù)說是為了預(yù)留給天城體梵文字母(Deva-nagari)的。印地文和尼泊爾文都使用了這種在 七世紀形成的字母表。由于印度定義了自己的編碼ISCII(Indian Script Code for Information Interchange),所以這個編號就未被使用。ISO 8859系列字符集都是單字節(jié)字符集,即只使用0x00-0xFF對字符編碼。 大家都知道ASCII吧,那么大家知道ANSI X3.4和ISO 646嗎?在1968年發(fā)布的ANSI X3.4和1972年發(fā)布的ISO 646就是ASCII編碼,只不過是不同組織發(fā)布的。絕大多數(shù)字符集都與ASCII編碼保持兼容,ISO 8859系列字符集也不例外,它們的0x00-0x7f都與ASCII碼保持一致,各字符集的不同之處在于如何利用0x80-0xff的碼位。使用 UniToy可以查看ISO 8859系列所有字符集的編碼,例如: 通過這些演示,大家是不是覺得代碼頁和字符集都是很簡單、樸實的東西呢?好,在進入Unicode的話題前,讓我們先看一個很深奧的概念。 3 字符編碼模型程序員經(jīng)常會面對復(fù)雜的問題,而降低復(fù)雜性的最簡單的方法就是分而治之。Peter Constable在他的文章"Character set encoding basics Understanding character set encodings and legacy encodings"中描述了字符編碼的四層模型。我覺得這種說法確實可以更清晰地展現(xiàn)字符編碼中發(fā)生的事情,所以在這里也介紹一下。 3.1 字符的范圍(Abstract character repertoire)設(shè)計字符編碼的第一層就是確定字符的范圍,即要支持哪些字符。有些編碼方案的字符范圍是固定的,例如ASCII、ISO 8859 系列。有些編碼方案的字符范圍是開放的,例如Unicode的字符范圍就是世界上所有的字符。 3.2 用數(shù)字表示字符(Coded character set)設(shè)計字符編碼的第二層是將字符和數(shù)字對應(yīng)起來??梢詫⑦@個層次理解成數(shù)學家(即從數(shù)學角度)看到的字符編碼。數(shù)學家看到的字符編碼是一個正整數(shù)。例如在Unicode中:漢字“字”對應(yīng)的數(shù)字是23383。漢字“”對應(yīng)的數(shù)字是134192。 在寫html文件時,可以通過輸入"字"來插入字符“字”。不過在設(shè)計字符編碼時,我們還是習慣用16進制表示數(shù)字。即將23383寫成0x5BD7,將134192寫成0x20C30。 3.3 用基本數(shù)據(jù)類型表示字符(Character encoding form)設(shè)計字符編碼的第三層是用編程語言中的基本數(shù)據(jù)類型來表示字符??梢詫⑦@個層次理解成程序員看到的字符編碼。在 Unicode中,我們有很多方式將數(shù)字23383表示成程序中的數(shù)據(jù),包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的縮寫,可以翻譯成Unicode字符集轉(zhuǎn)換格式,即怎樣將Unicode定義的數(shù)字轉(zhuǎn)換成程序數(shù)據(jù)。例如,“漢字”對應(yīng)的數(shù)字是 0x6c49和0x5b57,而編碼的程序數(shù)據(jù)是: BYTE data_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97}; // UTF-8編碼 這里用BYTE、WORD、DWORD分別表示無符號8位整數(shù),無符號16位整數(shù)和無符號32位整數(shù)。UTF-8、UTF-16、UTF-32分別以BYTE、WORD、DWORD作為編碼單位。 “漢字”的UTF-8編碼需要6個字節(jié)。“漢字”的UTF-16編碼需要兩個WORD,大小是4個字節(jié)。“漢字”的UTF-32編碼需要兩個DWORD,大小是8個字節(jié)。4.2節(jié)會介紹將數(shù)字映射到UTF編碼的規(guī)則。 3.4 作為字節(jié)流的字符(Character encoding scheme)字符編碼的第四層是計算機看到的字符,即在文件或內(nèi)存中的字節(jié)流。例如,“字”的UTF-32編碼是0x5b57,如果用little endian表示,字節(jié)流是“57 5b 00 00”。如果用big endian表示,字節(jié)流是“00 00 5b 57”。 字符編碼的第三層規(guī)定了一個字符由哪些編碼單位按什么順序表示。字符編碼的第四層在第三層的基礎(chǔ)上又考慮了編碼單位 內(nèi)部的字節(jié)序。UTF-8的編碼單位是字節(jié),不受字節(jié)序的影響。UTF-16、UTF-32根據(jù)字節(jié)序的不同,又衍生出UTF-16LE、UTF- 16BE、UTF-32LE、UTF-32BE四種編碼方案。LE和BE分別是Little Endian和Big Endian的縮寫。 3.5 小結(jié)通過四層模型,我們又把字符編碼中發(fā)生的這些事情梳理了一遍。其實大多數(shù)代碼頁都不需要完整的四層模型,例如GB18030以字節(jié)為編碼單位,直接規(guī)定了字節(jié)序列和字符的映射關(guān)系,跳過了第二層,也不需要第四層。 4 再談UnicodeUnicode是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。Unicode用數(shù)字 0-0x10FFFF來映射這些字符,最多可以容納1114112個字符,或者說有1114112個碼位。碼位就是可以分配給字符的數(shù)字。UTF-8、 UTF-16、UTF-32都是將數(shù)字轉(zhuǎn)換到程序數(shù)據(jù)的編碼方案。 Unicode字符集可以簡寫為UCS(Unicode Character Set)。早期的Unicode標準有UCS-2、UCS-4的說法。UCS-2用兩個字節(jié)編碼,UCS-4用4個字節(jié)編碼。UCS-4根據(jù)最高位為0的 最高字節(jié)分成2^7=128個group。每個group再根據(jù)次高字節(jié)分為256個平面(plane)。每個平面根據(jù)第3個字節(jié)分為256行 (row),每行有256個碼位(cell)。group 0的平面0被稱作BMP(Basic Multilingual Plane)。將UCS-4的BMP去掉前面的兩個零字節(jié)就得到了UCS-2。 Unicode標準計劃使用group 0 的17個平面: 從BMP(平面0)到平面16,即數(shù)字0-0x10FFFF?!墩?wù)刄nicode編碼》主要介紹了BMP的編碼,本文將介紹完整的Unicode編碼, 并從多個角度瀏覽Unicode。本文的介紹基于Unicode 5.0.0版本。 4.1 瀏覽Unicode先看一些數(shù)字:每個平面有2^16=65536個碼位。Unicode計劃使用了17個平面,一共有 17*65536=1114112個碼位。其實,現(xiàn)在已定義的碼位只有238605個,分布在平面0、平面1、平面2、平面14、平面15、平面16。其 中平面15和平面16上只是定義了兩個各占65534個碼位的專用區(qū)(Private Use Area),分別是0xF0000-0xFFFFD和0x100000-0x10FFFD。所謂專用區(qū),就是保留給大家放自定義字符的區(qū)域,可以簡寫為 PUA。 平面0也有一個專用區(qū):0xE000-0xF8FF,有6400個碼位。平面0的0xD800-0xDFFF,共2048個碼位,是一個被稱作代理區(qū)(Surrogate)的特殊區(qū)域。它的用途將在4.2節(jié)介紹。 238605-65534*2-6400-2408=99089。余下的99089個已定義碼位分布在平面0、平面 1、平面2和平面14上,它們對應(yīng)著Unicode目前定義的99089個字符,其中包括71226個漢字。平面0、平面1、平面2和平面14上分別定義 了52080、3419、43253和337個字符。平面2的43253個字符都是漢字。平面0上定義了27973個漢字。 在更深入地了解Unicode字符前,我們先了解一下UCD。 4.1.1 什么是UCDUCD是Unicode字符數(shù)據(jù)庫(Unicode Character Database)的縮寫。UCD由一些描述Unicode字符屬性和內(nèi)部關(guān)系的純文本或html文件組成。大家可以在Unicode組織的網(wǎng)站看到UCD的最新版本。 UCD中的文本文件大都是適合于程序分析的Unicode相關(guān)數(shù)據(jù)。其中的html文件解釋了數(shù)據(jù)庫的組織,數(shù)據(jù)的 格式和含義。UCD中最龐大的文件無疑就是描述漢字屬性的文件Unihan.txt。在UCD 5.0,0中,Unihan.txt文件大小有28,221K字節(jié)。Unihan.txt中包含了很多有參考價值的索引,例如漢字部首、筆劃、拼音、使用 頻度、四角號碼排序等。這些索引都是基于一些比較權(quán)威的辭典,但大多數(shù)索引只能檢索部分漢字。 我介紹UCD的目的主要是為了使用其中的兩個概念:Block和Script。 4.1.2 BlockUCD中的Blocks.txt將Unicode的碼位分割成一些連續(xù)的Block,并描述了每個Block的用途:
Block是Unicode字符的一個屬性。屬于同一個Block的字符有著相近的用途。Block表中的開始碼 位、結(jié)束碼位只是用來劃分出一塊區(qū)域,在開始碼位和結(jié)束碼位之間可能還有很多未定義的碼位。使用UniToy,大家可以按照Block瀏覽Unicode 字符,既可以按列表顯示: 也可以顯示每個字符的詳細信息:4.1.3 ScriptUnicode中每個字符都有一個Script屬性,這個屬性表明字符所屬的文字系統(tǒng)。Unicode目前支持以下Script:
其中,有兩個Script值有著特殊的含義:
UCD中的Script.txt列出了每個字符的Script屬性。使用UniToy可以按照Script屬性查看字符。例如: 左側(cè)Script窗口中,第一層節(jié)點是按英文字母順序排列的Script屬性。第二層節(jié)點是包含該Script文字的行(row),點擊后顯示該行內(nèi)屬于這個Script的字符。這樣,就可以集中查看屬于同一文字系統(tǒng)的字符。 4.1.4 Unicode中的漢字前面提過,在Unicode已定義的99089個字符中,有71226個字符是漢字。它們的分布如下:
UCD的Unihan.txt中的部首偏旁索引(kRSUnicode)可以檢索全部71226個漢字。 kRSUnicode的部首是按照康熙字典定義的,共214個部首。簡體字按照簡體部首對應(yīng)的繁體部首檢索。UniToy整理了康熙字典部首對應(yīng)的簡體部 首,提供了按照部首檢索漢字的功能: 4.2 UTF編碼在字符編碼的四個層次中,第一層的范圍和第二層的編碼在4.1節(jié)已經(jīng)詳細討論過了。本節(jié)討論第三層的UTF編碼和第四層的字節(jié)序,主要談?wù)劦谌龑拥腢TF編碼,即怎樣將Unicode定義的編碼轉(zhuǎn)換成程序數(shù)據(jù)。 4.2.1 UTF-8UTF-8以字節(jié)為單位對Unicode進行編碼。從Unicode到UTF-8的編碼方式如下:
UTF-8的特點是對不同范圍的字符使用不同長度的編碼。對于0x00-0x7F之間的字符,UTF-8編碼與 ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節(jié)。從上表可以看出,4字節(jié)模板有21個x,即可以容納21位二進制數(shù)字。Unicode的最 大碼位0x10FFFF也只有21位。 例1:“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用用3字節(jié)模板了:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。 例2:“”字的Unicode編碼是0x20C30。0x20C30在0x010000-0x10FFFF之間,使用用4字節(jié)模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。將0x20C30寫成21位二進制數(shù)字(不足21位就在前面補0):0 0010 0000 1100 0011 0000,用這個比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。 4.2.2 UTF-16UniToy有個“輸出編碼”功能,可以輸出當前選擇的文本編碼。因為UniToy內(nèi)部采用UTF-16編碼,所以 輸出的編碼就是文本的UTF-16編碼。例如:如果我們輸出“漢”字的UTF-16編碼,可以看到0x6C49,這與“漢”字的Unicode編碼是一致 的。如果我們輸出“”字的UTF-16編碼,可以看到0xD843, 0xDC30。“”字的Unicode編碼是0x20C30,它的UTF-16編碼是怎樣得到的呢? 4.2.2.1 編碼規(guī)則UTF-16編碼以16位無符號整數(shù)為單位。我們把Unicode編碼記作U。編碼規(guī)則如下:
為什么U‘可以被寫成20個二進制位?Unicode的最大碼位是0x10ffff,減去0x10000后,U‘的最大值是0xfffff,所以肯定可以用20個二進制位表示。例如:“”字的Unicode編碼是0x20C30,減去0x10000后,得到0x10C30,寫成二進制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。 4.2.2.2 代理區(qū)(Surrogate)按照上述規(guī)則,Unicode編碼0x10000-0x10FFFF的UTF-16編碼有兩個WORD,第一個WORD的高6位是110110,第二個WORD的高6位是110111??梢姡谝粋€WORD的取值范圍(二進制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二個WORD的取值范圍(二進制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。 為了將一個WORD的UTF-16編碼與兩個WORD的UTF-16編碼區(qū)分開來,Unicode編碼的設(shè)計者將0xD800-0xDFFF保留下來,并稱為代理區(qū)(Surrogate):
高位替代就是指這個范圍的碼位是兩個WORD的UTF-16編碼的第一個WORD。低位替代就是指這個范圍的碼位是 兩個WORD的UTF-16編碼的第二個WORD。那么,高位專用替代是什么意思?我們來解答這個問題,順便看看怎么由UTF-16編碼推導(dǎo) Unicode編碼。 解:如果一個字符的UTF-16編碼的第一個WORD在0xDB80到0xDBFF之間,那么它的Unicode編 碼在什么范圍內(nèi)?我們知道第二個WORD的取值范圍是0xDC00-0xDFFF,所以這個字符的UTF-16編碼范圍應(yīng)該是0xDB80 0xDC00到0xDBFF 0xDFFF。我們將這個范圍寫成二進制: 1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111 按照編碼的相反步驟,取出高低WORD的后10位,并拼在一起,得到 1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111 即0xe0000-0xfffff,按照編碼的相反步驟再加上0x10000,得到 0xf0000-0x10ffff。這就是UTF-16編碼的第一個WORD在0xdb80到0xdbff之間的Unicode編碼范圍,即平面15和平 面16。因為Unicode標準將平面15和平面16都作為專用區(qū),所以0xDB80到0xDBFF之間的保留碼位被稱作高位專用替代。 4.2.3 UTF-32UTF-32編碼以32位無符號整數(shù)為單位。Unicode的UTF-32編碼就是其對應(yīng)的32位無符號整數(shù)。 4.2.4 字節(jié)序根據(jù)字節(jié)序的不同,UTF-16可以被實現(xiàn)為UTF-16LE或UTF-16BE,UTF-32可以被實現(xiàn)為UTF-32LE或UTF-32BE。例如:
Unicode標準建議用BOM(Byte Order Mark)來區(qū)分字節(jié)序,即在傳輸字節(jié)流前,先傳輸被作為BOM的字符"零寬無中斷空格"。這個字符的編碼是FEFF,而反過來的FFFE(UTF- 16)和FFFE0000(UTF-32)在Unicode中都是未定義的碼位,不應(yīng)該出現(xiàn)在實際傳輸中。下表是各種UTF編碼的BOM:
5 結(jié)束語程序員的工作就是將復(fù)雜的世界簡單地表達出來,希望這篇文章也能做到這一點。本文的初稿完成于2007年2月14日。我會在我的個人主頁http://www.維護這篇文章的最新版本。 |
|