我曾經寫過一篇《談談Unicode編碼,簡要解釋UCS、UTF、BMP、BOM等名詞》(以下簡稱《談談Unicode編碼》),在網上流傳較廣,我也收到不少朋友的反饋。本文探討《談談Unicode編碼》中未介紹或介紹較少的代碼頁、Surrogates等問題,補充一些Unicode資料,順帶介紹一下我最近編寫的一個Unicode工具:UniToy。本文雖然是前文的補充,但在寫作上盡量做到獨立成篇。
標題中的“淺談”是對自己的要求,我希望文字能盡量淺顯易懂。但本文還是假設讀者知道字節、16進制,了解《談談Unicode編碼》中介紹過的字節序和Unicode的基本概念。
我們首先以Windows為例來看看文字顯示過程中發生了什么。用記事本打開一個文本文件,可以看到文件包含的文字:
如果我們用UltraEdit或Hex Workshop查看這個文件的16進制數據,可以看到:
我們看到:文件“例子GBK.txt”有10個字節,依次是“D7 D6 B7 FB BA CD B1 E0C2EB”,這就是記事本從文件中讀到的內容。記事本是用來打開文本文件的,所以它會調用Windows的文本顯示函數將讀到的數據作為文本顯示。Windows首先將文本數據轉換到它內部使用的編碼格式:Unicode,然后按照文本的Unicode去字體文件中查找字體圖像,最后將圖像顯示到窗口上。總結一下前面的分析,文字的顯示應該是這樣的:
如果上述3個步驟中任何一步發生了錯誤,文字就不能被正確顯示,例如:
錯誤1:如果弄錯了編碼,例如將Big5編碼的文字當作GBK編碼,就會出現亂碼。
錯誤2:如果從特定編碼到Unicode的映射發生錯誤,例如文本數據中出現該編碼方案未定義的字符,Windows就會使用缺省字符,通常是?。
在Unicode被廣泛使用前,有多少種語言、文字,就可能有多少種文字編碼方案。一種文字也可能有多種編碼方案。那么我們怎么確定文本數據采用了什么編碼?
按照慣例,文本文件中的數據都是文本編碼,那么它怎么表明自己的編碼格式?在記事本的“打開”對話框上:
我們可以看到記事本支持4種編碼格式:ANSI、Unicode、Unicode bigendian、UTF-8。如果讀者看過《談談Unicode編碼》,對Unicode、Unicode bigendian、UTF-8應該不會陌生,其實它們更準確的名稱應該是UTF-16LE(Little Endian)、UTF-16BE(BigEndian)和UTF-8,它們是基于Unicode的不同編碼方案。
在《談談Unicode編碼》中介紹過,Windows通過在文本文件開頭增加一些特殊字節(BOM)來區分上述3種編碼,并將沒有BOM的文本數據按照ANSI代碼頁處理。那么什么是代碼頁,什么是ANSI代碼頁?
代碼頁(Code Page)是個古老的專業術語,據說是IBM公司首先使用的。代碼頁和字符集的含義基本相同,代碼頁規定了適用于特定地區的字符集合,和這些字符的編碼。可以將代碼頁理解為字符和字節數據的映射表。
Windows為自己支持的代碼頁都編了一個號碼。例如代碼頁936就是簡體中文GBK,代碼頁950就是繁體中文Big5。代碼頁的概念比較簡單,就是一個字符編碼方案。但要說清楚Windows的ANSI代碼頁,就要從Windows的區域(Locale)說起了。
微軟為了適應世界上不同地區用戶的文化背景和生活習慣,在Windows中設計了區域(Locale)設置的功能。Local是指特定于某個國家或地區的一組設定,包括代碼頁,數字、貨幣、時間和日期的格式等。在Windows內部,其實有兩個Locale設置:系統Locale和用戶Locale。系統Locale決定代碼頁,用戶Locale決定數字、貨幣、時間和日期的格式。我們可以在控制面板的“區域和語言選項”中設置系統Locale和用戶Locale:
每個Locale都有一個對應的代碼頁。Locale和代碼頁的對應關系,大家可以參閱我的另一篇文章《談談Windows程序中的字符編碼》的附錄1。系統Locale對應的代碼頁被作為Windows的默認代碼頁。在沒有文本編碼信息時,Windows按照默認代碼頁的編碼方案解釋文本數據。這個默認代碼頁通常被稱作ANSI代碼頁(ACP)。
ANSI代碼頁還有一層意思,就是微軟自己定義的代碼頁。在歷史上,IBM的個人計算機和微軟公司的操作系統曾經是PC的標準配置。微軟公司將IBM公司定義的代碼頁稱作OEM代碼頁,在IBM公司的代碼頁基礎上作了些增補后,作為自己的代碼頁,并冠以ANSI的字樣。我們在“區域和語言選項”高級頁面的代碼頁轉換表中看到的包含ANSI字樣的代碼頁都是微軟自己定義的代碼頁。例如:
在UniToy中,我們可以按照代碼頁編碼順序查看這些代碼頁的字符和編碼:
我們不能直接設置ANSI代碼頁,只能通過選擇系統Locale,間接改變當前的ANSI代碼頁。微軟定義的Locale只使用自己定義的代碼頁。所以,我們雖然可以通過“區域和語言選項”中的代碼頁轉換表安裝很多代碼頁,但只能將微軟的代碼頁作為系統默認代碼頁。
在Windows2000以后,Windows統一采用UTF-16作為內部字符編碼。現在,安裝一個代碼頁就是安裝一張代碼頁轉換表。通過代碼頁轉換表,Windows既可以將代碼頁的編碼轉換到UTF-16,也可以將UTF-16轉換到代碼頁的編碼。代碼頁轉換表的具體實現可以是一個以nls為后綴的數據文件,也可以是一個提供轉換函數的動態鏈接庫。有的代碼頁是不需要安裝的。例如:Windows將UTF-7和UTF-8分別作為代碼頁65000和代碼頁65001。UTF-7、UTF-8和UTF-16都是基于Unicode的編碼方案。它們之間可以通過簡單的算法直接轉換,不需要安裝代碼頁轉換表。
在安裝過一個代碼頁后,Windows就知道怎樣將該代碼頁的文本轉換到Unicode文本,也知道怎樣將Unicode文本轉換成該代碼頁的文本。例如:UniToy有導入和導出功能。所謂導入功能就是將任一代碼頁的文本文件轉換到Unicode文本;導出功能就是將Unicode文本轉換到任一指定的代碼頁。這里所說的代碼頁就是指系統已安裝的代碼頁:
其實,如果全世界人民在計算機剛發明時就統一采用Unicode作為字符編碼,那么代碼頁就沒有存在的必要了。可惜在Unicode被發明前,世界各國人民都發明并使用了各種字符編碼方案。所以,Windows必須通過代碼頁支持已經被廣泛使用的字符編碼。從這種意義看,代碼頁主要是為了兼容現有的數據、程序和習慣而存在的。
SBCS、DBCS和MBCS分別是單字節字符集、雙字節字符集和多字節字符集的縮寫。SBCS、DBCS和MBCS的最大編碼長度分別是1字節、兩字節和大于兩字節(例如4或5字節)。例如:代碼頁1252 (ANSI-拉丁文I)是單字節字符集;代碼頁936 (ANSI/OEM-簡體中文 GBK)是雙字節字符集;代碼頁54936 (GB18030簡體中文)是多字節字符集。
單字節字符集中的字符都用一個字節表示。顯然,SBCS最多只能容納256個字符。
雙字節字符集的字符用一個或兩個字節表示。那么我們從文本數據中讀到一個字節時,怎么判斷它是單字節字符,還是雙字節字符的首字符?答案是通過字節所處范圍來判斷。例如:在GBK編碼中,單字節字符的范圍是0x00-0x80,雙字節字符首字節的范圍是0x81到0xFE。我們順序讀取字節數據,如果讀到的字節在0x81到0xFE內,那么這個字節就是雙字節字符的首字節。GBK定義雙字節字符的尾字節范圍是0x40到0x7E和0x80到0xFE。
GB18030是多字節字符集,它的字符可以用一個、兩個或四個字節表示。這時我們又如何判斷一個字節是屬于單字節字符,雙字節字符,還是四字節字符?GB18030與GBK是兼容的,它利用了GBK雙字節字符尾字節的未使用碼位。GB18030的四字節字符的第一字節的范圍也是0x81到0xFE,第二字節的范圍是0x30-0x39。通過第二字節所處范圍就可以區分雙字節字符和四字節字符。GB18030定義四字節字符的第三字節范圍是0x81到0xFE,第四字節范圍是0x30-0x39。
1.1節的“錯誤2”中演示了一個全被顯示成‘?‘的文件。這個文件的數據是:
其實,這是一個包含了6個四字節字符的GB18030編碼的文件。記事本按照GBK顯示這些數據,而GB18030的四字節字符編碼在GBK中是未定義的。Windows根據首字節范圍判斷出12個雙字節字符,然后因為找不到匹配的轉換而將其映射到默認字符‘?‘。使用UniToy按照GB18030代碼頁導入這個文件,就可以看到:
這個GB18030編碼的文件是用UniToy創建的,編輯Unicode文本,然后導出到GB18030編碼格式。
綜合使用UniToy的導入、導出功能就可以在任意兩個代碼頁之間轉換文本。其實,由于各代碼頁支持的字符范圍不同,我們一般不會直接在代碼頁間轉換文本。例如將以下GBK編碼的文本:
直接轉換到Big5編碼,就會看到:
變成‘?‘的字符都是Big5編碼不支持的簡化字。在從Unicode轉換到Big5編碼時,由于Big5編碼不支持這些字符,Windows就用默認字符‘?‘代替。在UniToy中,我們可以先將簡體字轉換到繁體字,然后再導出到Big5編碼,就可以正常顯示:
同理,將Big5編碼的文本轉換到GBK編碼的步驟應該是:
互聯網上的信息繽紛多彩,但文本依然是最重要的信息載體。html文件通過標記表明自己使用的字符集。例如:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
或者:
<meta http-equiv="charset" content="iso-8859-1">
那么我們可以使用哪些字符集(charset)呢?在IETF(互聯網工程任務組)的網頁上維護著一份可以在互聯網上使用的字符集的清單:CHARACTER SETS。如果有新的字符集被登記,IETF會更新這份文檔。
簡單瀏覽一下,2006年12月7日的版本列出了253個字符集。其中也包括微軟的CP1250 ~CP1258,在這里它們不會被稱作什么ANSI代碼頁,而是被簡單地稱作windows-1250、windows-1251等。其實在Unicode被廣泛使用前,除了中日韓等大字符集,世界上,特別是西方使用最廣泛的字符集應該是ISO 8859系列字符集。
ISO 8859系列字符集是歐洲計算機制造商協會(ECMA)在上世紀80年代中期設計,并被國際標準化(ISO)組織采納為國際標準。ISO 8859系列字符集目前有15個字符集,包括:
其中缺少的編號12據說是為了預留給天城體梵文字母(Deva-nagari)的。印地文和尼泊爾文都使用了這種在七世紀形成的字母表。由于印度定義了自己的編碼ISCII(Indian Script Code for InformationInterchange),所以這個編號就未被使用。ISO 8859系列字符集都是單字節字符集,即只使用0x00-0xFF對字符編碼。
大家都知道ASCII吧,那么大家知道ANSI X3.4和ISO 646嗎?在1968年發布的ANSIX3.4和1972年發布的ISO 646就是ASCII編碼,只不過是不同組織發布的。絕大多數字符集都與ASCII編碼保持兼容,ISO8859系列字符集也不例外,它們的0x00-0x7f都與ASCII碼保持一致,各字符集的不同之處在于如何利用0x80-0xff的碼位。使用UniToy可以查看ISO 8859系列所有字符集的編碼,例如:
通過這些演示,大家是不是覺得代碼頁和字符集都是很簡單、樸實的東西呢?好,在進入Unicode的話題前,讓我們先看一個很深奧的概念。
程序員經常會面對復雜的問題,而降低復雜性的最簡單的方法就是分而治之。Peter Constable在他的文章"Character set encoding basics Understanding character set encodings and legacy encodings"中描述了字符編碼的四層模型。我覺得這種說法確實可以更清晰地展現字符編碼中發生的事情,所以在這里也介紹一下。
設計字符編碼的第一層就是確定字符的范圍,即要支持哪些字符。有些編碼方案的字符范圍是固定的,例如ASCII、ISO 8859 系列。有些編碼方案的字符范圍是開放的,例如Unicode的字符范圍就是世界上所有的字符。
設計字符編碼的第二層是將字符和數字對應起來。可以將這個層次理解成數學家(即從數學角度)看到的字符編碼。數學家看到的字符編碼是一個正整數。例如在Unicode中:漢字“字”對應的數字是23383。漢字“
在寫html文件時,可以通過輸入"字"來插入字符“字”。不過在設計字符編碼時,我們還是習慣用16進制表示數字。即將23383寫成0x5BD7,將134192寫成0x20C30。
設計字符編碼的第三層是用編程語言中的基本數據類型來表示字符。可以將這個層次理解成程序員看到的字符編碼。在Unicode中,我們有很多方式將數字23383表示成程序中的數據,包括:UTF-8、UTF-16、UTF-32。UTF是“UCSTransformationFormat”的縮寫,可以翻譯成Unicode字符集轉換格式,即怎樣將Unicode定義的數字轉換成程序數據。例如,“漢字”對應的數字是0x6c49和0x5b57,而編碼的程序數據是:
BYTE data_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97}; // UTF-8編碼
WORD data_utf16[]={0x6c49,0x5b57}; // UTF-16編碼
DWORD data_utf32[]={0x6c49,0x5b57}; // UTF-32編碼
這里用BYTE、WORD、DWORD分別表示無符號8位整數,無符號16位整數和無符號32位整數。UTF-8、UTF-16、UTF-32分別以BYTE、WORD、DWORD作為編碼單位。
“漢字”的UTF-8編碼需要6個字節。“漢字”的UTF-16編碼需要兩個WORD,大小是4個字節。“漢字”的UTF-32編碼需要兩個DWORD,大小是8個字節。4.2節會介紹將數字映射到UTF編碼的規則。
字符編碼的第四層是計算機看到的字符,即在文件或內存中的字節流。例如,“字”的UTF-32編碼是0x5b57,如果用little endian表示,字節流是“57 5b 00 00”。如果用big endian表示,字節流是“00 00 5b 57”。
字符編碼的第三層規定了一個字符由哪些編碼單位按什么順序表示。字符編碼的第四層在第三層的基礎上又考慮了編碼單位內部的字節序。UTF-8的編碼單位是字節,不受字節序的影響。UTF-16、UTF-32根據字節序的不同,又衍生出UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE四種編碼方案。LE和BE分別是Little Endian和Big Endian的縮寫。
通過四層模型,我們又把字符編碼中發生的這些事情梳理了一遍。其實大多數代碼頁都不需要完整的四層模型,例如GB18030以字節為編碼單位,直接規定了字節序列和字符的映射關系,跳過了第二層,也不需要第四層。
Unicode是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。Unicode用數字0-0x10FFFF來映射這些字符,最多可以容納1114112個字符,或者說有1114112個碼位。碼位就是可以分配給字符的數字。UTF-8、UTF-16、UTF-32都是將數字轉換到程序數據的編碼方案。
Unicode字符集可以簡寫為UCS(Unicode CharacterSet)。早期的Unicode標準有UCS-2、UCS-4的說法。UCS-2用兩個字節編碼,UCS-4用4個字節編碼。UCS-4根據最高位為0的最高字節分成2^7=128個group。每個group再根據次高字節分為256個平面(plane)。每個平面根據第3個字節分為256行(row),每行有256個碼位(cell)。group 0的平面0被稱作BMP(Basic MultilingualPlane)。將UCS-4的BMP去掉前面的兩個零字節就得到了UCS-2。
Unicode標準計劃使用group 0 的17個平面:從BMP(平面0)到平面16,即數字0-0x10FFFF。《談談Unicode編碼》主要介紹了BMP的編碼,本文將介紹完整的Unicode編碼,并從多個角度瀏覽Unicode。本文的介紹基于Unicode 5.0.0版本。
先看一些數字:每個平面有2^16=65536個碼位。Unicode計劃使用了17個平面,一共有17*65536=1114112個碼位。其實,現在已定義的碼位只有238605個,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定義了兩個各占65534個碼位的專用區(Private UseArea),分別是0xF0000-0xFFFFD和0x100000-0x10FFFD。所謂專用區,就是保留給大家放自定義字符的區域,可以簡寫為PUA。
平面0也有一個專用區:0xE000-0xF8FF,有6400個碼位。平面0的0xD800-0xDFFF,共2048個碼位,是一個被稱作代理區(Surrogate)的特殊區域。它的用途將在4.2節介紹。
238605-65534*2-6400-2408=99089。余下的99089個已定義碼位分布在平面0、平面1、平面2和平面14上,它們對應著Unicode目前定義的99089個字符,其中包括71226個漢字。平面0、平面1、平面2和平面14上分別定義了52080、3419、43253和337個字符。平面2的43253個字符都是漢字。平面0上定義了27973個漢字。
在更深入地了解Unicode字符前,我們先了解一下UCD。
UCD是Unicode字符數據庫(Unicode Character Database)的縮寫。UCD由一些描述Unicode字符屬性和內部關系的純文本或html文件組成。大家可以在Unicode組織的網站看到UCD的最新版本。
UCD中的文本文件大都是適合于程序分析的Unicode相關數據。其中的html文件解釋了數據庫的組織,數據的格式和含義。UCD中最龐大的文件無疑就是描述漢字屬性的文件Unihan.txt。在UCD5.0,0中,Unihan.txt文件大小有28,221K字節。Unihan.txt中包含了很多有參考價值的索引,例如漢字部首、筆劃、拼音、使用頻度、四角號碼排序等。這些索引都是基于一些比較權威的辭典,但大多數索引只能檢索部分漢字。
我介紹UCD的目的主要是為了使用其中的兩個概念:Block和Script。
UCD中的Blocks.txt將Unicode的碼位分割成一些連續的Block,并描述了每個Block的用途:
開始碼位 | 結束碼位 | Block名稱(英文) | Block名稱(中文) |
0000 | 007F | Basic Latin | 基本拉丁字母 |
0080 | 00FF | Latin-1 Supplement | 拉丁字母補充-1 |
0100 | 017F | Latin Extended-A | 拉丁字母擴充-A |
0180 | 024F | Latin Extended-B | 拉丁字母擴充-B |
0250 | 02AF | IPA Extensions | 國際音標擴充 |
02B0 | 02FF | Spacing Modifier Letters | 進格修飾字符 |
0300 | 036F | Combining Diacritical Marks | 組合附加符號 |
0370 | 03FF | Greek and Coptic | 希臘文和哥普特文 |
0400 | 04FF | Cyrillic | 西里爾文 |
0500 | 052F | Cyrillic Supplement | 西里爾文補充 |
0530 | 058F | Armenian | 亞美尼亞文 |
0590 | 05FF | Hebrew | 希伯來文 |
0600 | 06FF | Arabic | 基本阿拉伯文 |
0700 | 074F | Syriac | 敘利亞文 |
0750 | 077F | Arabic Supplement | 阿拉伯文補充 |
0780 | 07BF | Thaana | 塔納文 |
07C0 | 07FF | NKo | N‘Ko字母表 |
0900 | 097F | Devanagari | 天成文書(梵文) |
0980 | 09FF | Bengali | 孟加拉文 |
0A00 | 0A7F | Gurmukhi | 錫克教文 |
0A80 | 0AFF | Gujarati | 古吉拉特文 |
0B00 | 0B7F | Oriya | 奧里亞文 |
0B80 | 0BFF | Tamil | 泰米爾文 |
0C00 | 0C7F | Telugu | 泰盧固文 |
0C80 | 0CFF | Kannada | 卡納達文 |
0D00 | 0D7F | Malayalam | 德拉維族文 |
0D80 | 0DFF | Sinhala | 僧伽羅文 |
0E00 | 0E7F | Thai | 泰文 |
0E80 | 0EFF | Lao | 老撾文 |
0F00 | 0FFF | Tibetan | 藏文 |
1000 | 109F | Myanmar | 緬甸文 |
10A0 | 10FF | Georgian | 格魯吉亞文 |
1100 | 11FF | Hangul Jamo | 朝鮮文 |
1200 | 137F | Ethiopic | 埃塞俄比亞文 |
1380 | 139F | Ethiopic Supplement | 埃塞俄比亞文補充 |
13A0 | 13FF | Cherokee | 切羅基文 |
1400 | 167F | Unified Canadian Aboriginal Syllabics | 加拿大印第安方言 |
1680 | 169F | Ogham | 歐甘文 |
16A0 | 16FF | Runic | 北歐古字 |
1700 | 171F | Tagalog | 塔加路文 |
1720 | 173F | Hanunoo | 哈努諾文 |
1740 | 175F | Buhid | 布迪文 |
1760 | 177F | Tagbanwa | Tagbanwa文 |
1780 | 17FF | Khmer | 高棉文 |
1800 | 18AF | Mongolian | 蒙古文 |
1900 | 194F | Limbu | 林布文 |
1950 | 197F | Tai Le | 德宏傣文 |
1980 | 19DF | New Tai Lue | 新傣文 |
19E0 | 19FF | Khmer Symbols | 高棉文 |
1A00 | 1A1F | Buginese | 布吉文 |
1B00 | 1B7F | Balinese | 巴厘文 |
1D00 | 1D7F | Phonetic Extensions | 拉丁字母音標擴充 |
1D80 | 1DBF | Phonetic Extensions Supplement | 拉丁字母音標擴充增補 |
1DC0 | 1DFF | Combining Diacritical Marks Supplement | 組合附加符號補充 |
1E00 | 1EFF | Latin Extended Additional | 拉丁字母擴充附加 |
1F00 | 1FFF | Greek Extended | 希臘文擴充 |
2000 | 206F | General Punctuation | 一般標點符號 |
2070 | 209F | Superscripts and Subscripts | 上標和下標 |
20A0 | 20CF | Currency Symbols | 貨幣符號 |
20D0 | 20FF | Combining Diacritical Marks for Symbols | 符號用組合附加符號 |
2100 | 214F | Letterlike Symbols | 似字母符號 |
2150 | 218F | Number Forms | 數字形式 |
2190 | 21FF | Arrows | 箭頭符號 |
2200 | 22FF | Mathematical Operators | 數學運算符號 |
2300 | 23FF | Miscellaneous Technical | 零雜技術用符號 |
2400 | 243F | Control Pictures | 控制圖符 |
2440 | 245F | Optical Character Recognition | 光學字符識別 |
2460 | 24FF | Enclosed Alphanumerics | 帶括號的字母數字 |
2500 | 257F | Box Drawing | 制表符 |
2580 | 259F | Block Elements | 方塊元素 |
25A0 | 25FF | Geometric Shapes | 幾何形狀 |
2600 | 26FF | Miscellaneous Symbols | 零雜符號 |
2700 | 27BF | Dingbats | 雜錦字型 |
27C0 | 27EF | Miscellaneous Mathematical Symbols-A | 零雜數學符號-A |
27F0 | 27FF | Supplemental Arrows-A | 箭頭符號補充-A |
2800 | 28FF | Braille Patterns | 盲文 |
2900 | 297F | Supplemental Arrows-B | 箭頭符號補充-B |
2980 | 29FF | Miscellaneous Mathematical Symbols-B | 零雜數學符號-B |
2A00 | 2AFF | Supplemental Mathematical Operators | 數學運算符號 |
2B00 | 2BFF | Miscellaneous Symbols and Arrows | 零雜符號和箭頭 |
2C00 | 2C5F | Glagolitic | 格拉哥里字母表 |
2C60 | 2C7F | Latin Extended-C | 拉丁字母擴充-C |
2C80 | 2CFF | Coptic | 科普特文 |
2D00 | 2D2F | Georgian Supplement | 格魯吉亞文補充 |
2D30 | 2D7F | Tifinagh | 提非納字母 |
2D80 | 2DDF | Ethiopic Extended | 埃塞俄比亞文擴充 |
2E00 | 2E7F | Supplemental Punctuation | 標點符號補充 |
2E80 | 2EFF | CJK Radicals Supplement | 中日韓部首補充 |
2F00 | 2FDF | Kangxi Radicals | 康熙字典部首 |
2FF0 | 2FFF | Ideographic Description Characters | 漢字結構描述字符 |
3000 | 303F | CJK Symbols and Punctuation | 中日韓符號和標點 |
3040 | 309F | Hiragana | 平假名 |
30A0 | 30FF | Katakana | 片假名 |
3100 | 312F | Bopomofo | 注音符號 |
3130 | 318F | Hangul Compatibility Jamo | 朝鮮文兼容字母 |
3190 | 319F | Kanbun | 日文的漢字批注 |
31A0 | 31BF | Bopomofo Extended | 注音符號擴充 |
31C0 | 31EF | CJK Strokes | 中日韓筆劃 |
31F0 | 31FF | Katakana Phonetic Extensions | 片假名音標擴充 |
3200 | 32FF | Enclosed CJK Letters and Months | 帶括號的中日韓字母及月份 |
3300 | 33FF | CJK Compatibility | 中日韓兼容字符 |
3400 | 4DBF | CJK Unified Ideographs Extension A | 中日韓統一表意文字擴充A |
4DC0 | 4DFF | Yijing Hexagram Symbols | 易經六十四卦象 |
4E00 | 9FFF | CJK Unified Ideographs | 中日韓統一表意文字 |
A000 | A48F | Yi Syllables | 彝文音節 |
A490 | A4CF | Yi Radicals | 彝文字根 |
A700 | A71F | Modifier Tone Letters | 聲調修飾字母 |
A720 | A7FF | Latin Extended-D | 拉丁字母擴充-D |
A800 | A82F | Syloti Nagri | Syloti Nagri字母表 |
A840 | A87F | Phags-pa | Phags-pa字母表 |
AC00 | D7AF | Hangul Syllables | 朝鮮文音節 |
D800 | DB7F | High Surrogates | 高位替代 |
DB80 | DBFF | High Private Use Surrogates | 高位專用替代 |
DC00 | DFFF | Low Surrogates | 低位替代 |
E000 | F8FF | Private Use Area | 專用區 |
F900 | FAFF | CJK Compatibility Ideographs | 中日韓兼容表意文字 |
FB00 | FB4F | Alphabetic Presentation Forms | 字母變體顯現形式 |
FB50 | FDFF | Arabic Presentation Forms-A | 阿拉伯文變體顯現形式-A |
FE00 | FE0F | Variation Selectors | 字型變換選取器 |
FE10 | FE1F | Vertical Forms | 豎排標點符號 |
FE20 | FE2F | Combining Half Marks | 組合半角標示 |
FE30 | FE4F | CJK Compatibility Forms | 中日韓兼容形式 |
FE50 | FE6F | Small Form Variants | 小型變體形式 |
FE70 | FEFF | Arabic Presentation Forms-B | 阿拉伯文變體顯現形式-B |
FF00 | FFEF | Halfwidth and Fullwidth Forms | 半角及全角字符 |
FFF0 | FFFF | Specials | 特殊區域 |
10000 | 1007F | Linear B Syllabary | 線形文字B音節文字 |
10080 | 100FF | Linear B Ideograms | 線形文字B表意文字 |
10100 | 1013F | Aegean Numbers | 愛琴海數字 |
10140 | 1018F | Ancient Greek Numbers | 古希臘數字 |
10300 | 1032F | Old Italic | 古意大利文 |
10330 | 1034F | Gothic | 哥特文 |
10380 | 1039F | Ugaritic | 烏加里特楔形文字 |
103A0 | 103DF | Old Persian | 古波斯文 |
10400 | 1044F | Deseret | 德塞雷特大學音標 |
10450 | 1047F | Shavian | 肅伯納速記符號 |
10480 | 104AF | Osmanya | Osmanya字母表 |
10800 | 1083F | Cypriot Syllabary | 塞浦路斯音節文字 |
10900 | 1091F | Phoenician | 腓尼基文 |
10A00 | 10A5F | Kharoshthi | 迦婁士悌文 |
12000 | 123FF | Cuneiform | 楔形文字 |
12400 | 1247F | Cuneiform Numbers and Punctuation | 楔形文字數字和標點 |
1D000 | 1D0FF | Byzantine Musical Symbols | 東正教音樂符號 |
1D100 | 1D1FF | Musical Symbols | 音樂符號 |
1D200 | 1D24F | Ancient Greek Musical Notation | 古希臘音樂符號 |
1D300 | 1D35F | Tai Xuan Jing Symbols | 太玄經符號 |
1D360 | 1D37F | Counting Rod Numerals | 算籌 |
1D400 | 1D7FF | Mathematical Alphanumeric Symbols | 數學用字母數字符號 |
20000 | 2A6DF | CJK Unified Ideographs Extension B | 中日韓統一表意文字擴充 B |
2F800 | 2FA1F | CJK Compatibility Ideographs Supplement | 中日韓兼容表意文字補充 |
E0000 | E007F | Tags | 標簽 |
E0100 | E01EF | Variation Selectors Supplement | 字型變換選取器補充 |
F0000 | FFFFF | Supplementary Private Use Area-A | 補充專用區-A |
100000 | 10FFFF | Supplementary Private Use Area-B | 補充專用區-B |
Block是Unicode字符的一個屬性。屬于同一個Block的字符有著相近的用途。Block表中的開始碼位、結束碼位只是用來劃分出一塊區域,在開始碼位和結束碼位之間可能還有很多未定義的碼位。使用UniToy,大家可以按照Block瀏覽Unicode字符,既可以按列表顯示:
Unicode中每個字符都有一個Script屬性,這個屬性表明字符所屬的文字系統。Unicode目前支持以下Script:
Script名稱(英文) | Script名稱(中文) | Script包含的字符數 |
Arabic | 阿拉伯文 | 966 |
Armenian | 亞美尼亞文 | 90 |
Balinese | 巴厘文 | 121 |
Bengali | 孟加拉文 | 91 |
Bopomofo | 漢語注音符號 | 64 |
Braille | 盲文 | 256 |
Buginese | 布吉文 | 30 |
Buhid | 布迪文 | 20 |
Canadian Aboriginal | 加拿大印第安方言 | 630 |
Cherokee | 切羅基文 | 85 |
Common | Common | 5020 |
Coptic | 科普特文 | 128 |
Cuneiform | 楔形文字 | 982 |
Cypriot | 塞浦路斯音節文字 | 55 |
Cyrillic | 西里爾文 | 277 |
Deseret | 德塞雷特大學音標 | 80 |
Devanagari | 天成文書(梵文) | 107 |
Ethiopic | 埃塞俄比亞文 | 461 |
Georgian | 格魯吉亞文 | 120 |
Gothic | 哥特文 | 94 |
Glagolitic | 格拉哥里字母表 | 27 |
Greek | 希臘文 | 506 |
Gujarati | 古吉拉特文 | 83 |
Gurmukhi | 錫克教文 | 77 |
Han | 漢文 | 71570 |
Hangul | 韓文書寫系統 | 11619 |
Hanunoo | 哈努諾文 | 21 |
Hebrew | 希伯來文 | 133 |
Hiragana | 平假名 | 89 |
Inherited | Inherited | 461 |
Kannada | 卡納達文 | 86 |
Katakana | 片假名 | 164 |
Kharoshthi | 迦婁士悌文 | 65 |
Khmer | 高棉文 | 146 |
Lao | 老撾文 | 65 |
Latin | 拉丁文系 | 1070 |
Limbu | 林布文(尼泊爾東部) | 66 |
Linear B | 線形文字B | 211 |
Malayalam | 德拉維族文(印度) | 78 |
Mongolian | 蒙古文 | 152 |
Myanmar | 緬甸文 | 78 |
New Tai Lue | 新傣文 | 80 |
Nko | N‘Ko字母表 | 59 |
Ogham | 歐甘文字 | 29 |
Old Italic | 古意大利文 | 35 |
Old Persian | 古波斯文 | 50 |
Oriya | 奧里亞文 | 81 |
Osmanya | Osmanya字母表 | 40 |
Phags Pa | Phags Pa字母表(蒙古) | 56 |
Phoenician | 腓尼基文 | 27 |
Runic | 古代北歐文 | 78 |
Shavian | 肅伯納速記符號 | 48 |
Sinhala | 僧伽羅文 | 80 |
Syloti Nagri | Syloti Nagri字母表(印度) | 44 |
Syriac | 敘利亞文 | 77 |
Tagalog | 塔加路文(菲律賓) | 20 |
Tagbanwa | Tagbanwa文(菲律賓) | 18 |
Tai Le | 德宏傣文 | 35 |
Tamil | 泰米爾文 | 71 |
Telugu | 泰盧固文(印度) | 80 |
Thaana | 馬爾代夫書寫體 | 50 |
Thai | 泰國文 | 86 |
Tibetan | 藏文 | 195 |
Tifinagh | 提非納字母表 | 55 |
Ugaritic | 烏加里特楔形文字 | 31 |
Yi | 彝文 | 1220 |
其中,有兩個Script值有著特殊的含義:
UCD中的Script.txt列出了每個字符的Script屬性。使用UniToy可以按照Script屬性查看字符。例如:
左側Script窗口中,第一層節點是按英文字母順序排列的Script屬性。第二層節點是包含該Script文字的行(row),點擊后顯示該行內屬于這個Script的字符。這樣,就可以集中查看屬于同一文字系統的字符。
前面提過,在Unicode已定義的99089個字符中,有71226個字符是漢字。它們的分布如下:
Block名稱 | 開始碼位 | 結束碼位 | 數量 | |
中日韓統一表意文字擴充A | 3400 | 4db5 | 6582 | |
中日韓統一表意文字 | 4e00 | 9fbb | 20924 | |
中日韓兼容表意文字 | f900 | fa2d | 302 | |
中日韓兼容表意文字 | fa30 | fa6a | 59 | |
中日韓兼容表意文字 | fa70 | fad9 | 106 | |
中日韓統一表意文字擴充B | 20000 | 2a6d6 | 42711 | |
中日韓兼容表意文字補充 | 2f800 | 2fa1d | 542 |
UCD的Unihan.txt中的部首偏旁索引(kRSUnicode)可以檢索全部71226個漢字。kRSUnicode的部首是按照康熙字典定義的,共214個部首。簡體字按照簡體部首對應的繁體部首檢索。UniToy整理了康熙字典部首對應的簡體部首,提供了按照部首檢索漢字的功能:
在字符編碼的四個層次中,第一層的范圍和第二層的編碼在4.1節已經詳細討論過了。本節討論第三層的UTF編碼和第四層的字節序,主要談談第三層的UTF編碼,即怎樣將Unicode定義的編碼轉換成程序數據。
UTF-8以字節為單位對Unicode進行編碼。從Unicode到UTF-8的編碼方式如下:
Unicode編碼(16進制) | UTF-8 字節流(二進制) |
000000 - 00007F | 0xxxxxxx |
000080 - 0007FF | 110xxxxx 10xxxxxx |
000800 - 00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000 - 10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8的特點是對不同范圍的字符使用不同長度的編碼。對于0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節。從上表可以看出,4字節模板有21個x,即可以容納21位二進制數字。Unicode的最大碼位0x10FFFF也只有21位。
例1:“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
例2:“
UniToy有個“輸出編碼”功能,可以輸出當前選擇的文本編碼。因為UniToy內部采用UTF-16編碼,所以輸出的編碼就是文本的UTF-16編碼。例如:如果我們輸出“漢”字的UTF-16編碼,可以看到0x6C49,這與“漢”字的Unicode編碼是一致的。如果我們輸出“
UTF-16編碼以16位無符號整數為單位。我們把Unicode編碼記作U。編碼規則如下:
為什么U‘可以被寫成20個二進制位?Unicode的最大碼位是0x10ffff,減去0x10000后,U‘的最大值是0xfffff,所以肯定可以用20個二進制位表示。例如:“
按照上述規則,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編碼區分開來,Unicode編碼的設計者將0xD800-0xDFFF保留下來,并稱為代理區(Surrogate):
D800 | DB7F | High Surrogates | 高位替代 |
DB80 | DBFF | High Private Use Surrogates | 高位專用替代 |
DC00 | DFFF | Low Surrogates | 低位替代 |
高位替代就是指這個范圍的碼位是兩個WORD的UTF-16編碼的第一個WORD。低位替代就是指這個范圍的碼位是兩個WORD的UTF-16編碼的第二個WORD。那么,高位專用替代是什么意思?我們來解答這個問題,順便看看怎么由UTF-16編碼推導Unicode編碼。
解:如果一個字符的UTF-16編碼的第一個WORD在0xDB80到0xDBFF之間,那么它的Unicode編碼在什么范圍內?我們知道第二個WORD的取值范圍是0xDC00-0xDFFF,所以這個字符的UTF-16編碼范圍應該是0xDB800xDC00到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都作為專用區,所以0xDB80到0xDBFF之間的保留碼位被稱作高位專用替代。
UTF-32編碼以32位無符號整數為單位。Unicode的UTF-32編碼就是其對應的32位無符號整數。
根據字節序的不同,UTF-16可以被實現為UTF-16LE或UTF-16BE,UTF-32可以被實現為UTF-32LE或UTF-32BE。例如:
字符 | Unicode編碼 | UTF-16LE | UTF-16BE | UTF32-LE | UTF32-BE |
漢 | 0x6C49 | 49 6C | 6C 49 | 49 6C 00 00 | 00 00 6C 49 |
![]() | 0x20C30 | 43 D8 30 DC | D8 43 DC 30 | 30 0C 02 00 | 00 02 0C 30 |
Unicode標準建議用BOM(Byte OrderMark)來區分字節序,即在傳輸字節流前,先傳輸被作為BOM的字符"零寬無中斷空格"。這個字符的編碼是FEFF,而反過來的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定義的碼位,不應該出現在實際傳輸中。下表是各種UTF編碼的BOM:
UTF編碼 | Byte Order Mark |
UTF-8 | EF BB BF |
UTF-16LE | FF FE |
UTF-16BE | FE FF |
UTF-32LE | FF FE 00 00 |
UTF-32BE | 00 00 FE FF |
程序員的工作就是將復雜的世界簡單地表達出來,希望這篇文章也能做到這一點。本文的初稿完成于2007年2月14日。我會在我的個人主頁http://www.fmddlmyy.cn維護這篇文章的最新版本。