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