[轉帖] 談談Unicode編碼,簡要解釋UCS、UTF、BMP、BOM等名詞

這是一篇程序員寫給程序員的趣味讀物。所謂趣味是指可以比較輕鬆地瞭解一些原來不清楚的概念,增進知識,類似於打RPG遊戲的升級。整理這篇文章的動機是兩個問題:

問題一:
使用Windows記事本的“另存爲”,可以在GBK、Unicode、Unicode big endian和UTF-8這幾種編碼方式間相互轉換。同樣是txt文件,Windows是怎樣識別編碼方式的呢?

我很早前就發現Unicode、Unicode big endian和UTF-8編碼的txt文件的開頭會多出幾個字節,分別是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但這些標記是基於什麼標準呢?

問題二:
最近在網上看到一個ConvertUTF.c,實現了UTF-32、UTF-16和UTF-8這三種編碼方式的相互轉換。對於Unicode(UCS2)、GBK、UTF-8這些編碼方式,我原來就瞭解。但這個程序讓我有些糊塗,想不起來UTF-16和UCS2有什麼關係。
查了查相關資料,總算將這些問題弄清楚了,順帶也瞭解了一些Unicode的細節。寫成一篇文章,送給有過類似疑問的朋友。本文在寫作時儘量做到通俗易懂,但要求讀者知道什麼是字節,什麼是十六進制。

0、big endian和little endian
big endian和little endian是CPU處理多字節數的不同方式。例如“漢”字的Unicode編碼是6C49。那麼寫到文件裏時,究竟是將6C寫在前面,還是將49寫在前面?如果將6C寫在前面,就是big endian。還是將49寫在前面,就是little endian。

“endian”這個詞出自《格列佛遊記》。小人國的內戰就源於喫雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。

我們一般將endian翻譯成“字節序”,將big endian和little endian稱作“大尾”和“小尾”。

1、字符編碼、內碼,順帶介紹漢字編碼
字符必須編碼後才能被計算機處理。計算機使用的缺省編碼方式就是計算機的內碼。早期的計算機使用7位的ASCII編碼,爲了處理漢字,程序員設計了用於簡體中文的GB2312和用於繁體中文的big5。

GB2312(1980年)一共收錄了7445個字符,包括6763個漢字和682個其它符號。漢字區的內碼範圍高字節從B0-F7,低字節從A1-FE,佔用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。

GB2312支持的漢字太少。1995年的漢字擴展規範GBK1.0收錄了21886個符號,它分爲漢字區和圖形符號區。漢字區包括21003個字符。2000年的GB18030是取代GBK1.0的正式國家標準。該標準收錄了27484個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。現在的PC平臺必須支持GB18030,對嵌入式產品暫不作要求。所以手機、MP3一般只支持GB2312。

從ASCII、GB2312、GBK到GB18030,這些編碼方法是向下兼容的,即同一個字符在這些方案中總是有相同的編碼,後面的標準支持更多的字符。在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高字節的最高位不爲0。按照程序員的稱呼,GB2312、GBK到GB18030都屬於雙字節字符集 (DBCS)。

有的中文Windows的缺省內碼還是GBK,可以通過GB18030升級包升級到GB18030。不過GB18030相對GBK增加的字符,普通人是很難用到的,通常我們還是用GBK指代中文Windows內碼。

這裏還有一些細節:

GB2312的原文還是區位碼,從區位碼到內碼,需要在高字節和低字節上分別加上A0。

在DBCS中,GB內碼的存儲格式始終是big endian,即高位在前。

GB2312的兩個字節的最高位都是1。但符合這個條件的碼位只有128*128=16384個。所以GBK和GB18030的低字節最高位都可能不是1。不過這不影響DBCS字符流的解析:在讀取DBCS字符流時,只要遇到高位爲1的字節,就可以將下兩個字節作爲一個雙字節編碼,而不用管低字節的高位是什麼。

2、Unicode、UCS和UTF
前面提到從ASCII、GB2312、GBK到GB18030的編碼方法是向下兼容的。而Unicode只與ASCII兼容(更準確地說,是與ISO-8859-1兼容),與GB碼不兼容。例如“漢”字的Unicode編碼是6C49,而GB碼是BABA。

Unicode也是一種字符編碼方法,不過它是由國際組織設計,可以容納全世界所有語言文字的編碼方案。Unicode的學名是"Universal Multiple-Octet Coded Character Set",簡稱爲UCS。UCS可以看作是"Unicode Character Set"的縮寫。

根據維基百科全書(http://zh.wikipedia.org/wiki/)的記載:歷史上存在兩個試圖獨立設計Unicode的組織,即國際標準化組織(ISO)和一個軟件製造商的協會(unicode.org)。ISO開發了ISO 10646項目,Unicode協會開發了Unicode項目。

在1991年前後,雙方都認識到世界不需要兩個不兼容的字符集。於是它們開始合併雙方的工作成果,併爲創立一個單一編碼表而協同工作。從Unicode2.0開始,Unicode項目採用了與ISO 10646-1相同的字庫和字碼。

目前兩個項目仍都存在,並獨立地公佈各自的標準。Unicode協會現在的最新版本是2005年的Unicode 4.1.0。ISO的最新標準是10646-3:2003。

UCS規定了怎麼用多個字節表示各種文字。怎樣傳輸這些編碼,是由UTF(UCS Transformation format)規範規定的,常見的UTF規範包括UTF-8、UTF-7、UTF-16。

IETF的RFC2781和RFC3629以RFC的一貫風格,清晰、明快又不失嚴謹地描述了UTF-16和UTF-8的編碼方法。我總是記不得IETF是Internet Engineering Task Force的縮寫。但IETF負責維護的RFC是Internet上一切規範的基礎。

3、UCS-2、UCS-4、BMP

UCS有兩種格式:UCS-2和UCS-4。顧名思義,UCS-2就是用兩個字節編碼,UCS-4就是用4個字節(實際上只用了31位,最高位必須爲0)編碼。下面讓我們做一些簡單的數學遊戲:

UCS-2有2^16=65536個碼位,UCS-4有2^31=2147483648個碼位。

UCS-4根據最高位爲0的最高字節分成2^7=128個group。每個group再根據次高字節分爲256個plane。每個plane根據第3個字節分爲256行 (rows),每行包含256個cells。當然同一行的cells只是最後一個字節不同,其餘都相同。

group 0的plane 0被稱作Basic Multilingual Plane, 即BMP。或者說UCS-4中,高兩個字節爲0的碼位被稱作BMP。

將UCS-4的BMP去掉前面的兩個零字節就得到了UCS-2。在UCS-2的兩個字節前加上兩個零字節,就得到了UCS-4的BMP。而目前的UCS-4規範中還沒有任何字符被分配在BMP之外。

4、UTF編碼

UTF-8就是以8位爲單元對UCS進行編碼。從UCS-2到UTF-8的編碼方式如下:

UCS-2編碼(16進制) UTF-8 字節流(二進制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

讀者可以用記事本測試一下我們的編碼是否正確。

UTF-16以16位爲單元對UCS進行編碼。對於小於0x10000的UCS碼,UTF-16編碼就等於UCS碼對應的16位無符號整數。對於不小於0x10000的UCS碼,定義了一個算法。不過由於實際使用的UCS2,或者UCS4的BMP必然小於0x10000,所以就目前而言,可以認爲UTF-16和UCS-2基本相同。但UCS-2只是一個編碼方案,UTF-16卻要用於實際的傳輸,所以就不得不考慮字節序的問題。

5、UTF的字節序和BOM
UTF-8以字節爲編碼單元,沒有字節序的問題。UTF-16以兩個字節爲編碼單元,在解釋一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16字節流“594E”,那麼這是“奎”還是“乙”?

Unicode規範中推薦的標記字節順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:

在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸字節流前,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"。

這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。

UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。

Windows就是使用BOM來標記文本文件的編碼方式的。

6、進一步的參考資料
本文主要參考的資料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。

我還找了兩篇看上去不錯的資料,不過因爲我開始的疑問都找到了答案,所以就沒有看:

"Understanding Unicode A general introduction to the Unicode Standard" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and legacy encodings" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)
我寫過UTF-8、UCS-2、GBK相互轉換的軟件包,包括使用Windows API和不使用Windows API的版本。以後有時間的話,我會整理一下放到我的個人主頁上(http://fmddlmyy.home4u.china.com)。

我是想清楚所有問題後纔開始寫這篇文章的,原以爲一會兒就能寫好。沒想到考慮措辭和查證細節花費了很長時間,竟然從下午1:30寫到9:00。希望有讀者能從中受益。


http://dev.csdn.net/develop/article/69/69883.shtm

 

 

 

 

 

 

 

編碼問題有時候確實比較麻煩,我最近也看了一些編碼方面的資料,下面的這篇也介紹給比較好,主要着重於編碼轉換,大家可以看看。
=====================================
GB碼與BIG5是中國人常用的兩種編碼集。GB碼爲大陸使用,BIG5爲香港與臺灣使用。每個編碼都由2個字符構成,高字節在前,低字節在後。下面我將使用Python實現的編碼轉換的程序向大家作一個介紹。關於編碼的一些知識大家可以去網上查找,本人不再贅述。 GB碼是大陸使用的編碼集。以前使用的爲GB-2312編程,它只有常用字,字數有限。後國家制定了新的GBK編碼,漢字已經達到了2萬多。GBK完全兼容原GB-2312編碼,也就是說一個GB2312的編碼在GBK上是一模一樣的。這裏所介紹的轉換是以GBK爲基礎的,因此適用性很廣。GBK編碼中不僅包括了原GB-2312編碼,同時也包括了許多簡碼的繁體碼,同時還有許多的符號與不常用漢字。GBK編碼的範圍是:高字節從0x81到0xFE,低字節從0x40到0xFE,同時不包括0x7F。這樣如果我們將其排成一個矩形,看上去就少了xx7F一根線。

編碼的定位
那麼如何定位一個GBK碼呢?當我們拿到一個編碼時,如何判斷是不是一個GBK碼,如果是GBK碼如何定位它的位置呢?

判斷一個GBK碼應該比較簡單,我們只要根據它的有效範圍進行判定即可。如:

if 0x81<=ch1<=0xFE and (0x40<=ch2<=0x7E or 0x7E<=ch2<=0xFE): #is gb char


這裏ch1和ch2分別是一個字符的高字節和低字節。

如何定位(爲什麼要定位我們在後面講)?首先介紹一下碼錶。碼錶是所有編碼放在一起形成的,你可以將其放在文件中(這裏講述的是將編碼放在文件中)。我們在存放編碼時是將有實際意義的編碼放在了一起(因爲有一些組合是不存在的),而且是按字節大小的順序放的。根據GBK的編碼範圍,我們可以設想一個二維座標,縱座標是高字節,橫座標是低字節,每一個交叉點上是一個漢字,佔兩個字節。這樣一行上的漢字個數應該爲0xFE-0x40+1-1=190(加1是因爲要把0x40也算進去。減1是因爲要把7F去掉)。定位時,我們先用高字節減去0x81,得到縱座標偏移量。用低字節減去0x40得到橫座標偏移量。用縱座標偏移量乘以每個漢字個數,加上橫座標偏移量就得到漢字的偏移量。再乘以2得到字節的偏移量。那麼定位算法爲:

index=((ch1-0x81)*190+(ch2-0x40)-(ch2/128))*2


上面的算法中有-(ch2/128)。這是因爲GBK中沒有7F碼,因此當ch2小於7F時,ch2/128=0,則表示7F沒有計算在內。而當ch2大於7F時,ch2/128=1,則表示多算了7F一值,因此要去掉。由於一個漢字有兩個字節,故要乘以2。這樣我們就得到一個GBK漢字在碼錶中的字節位置了。

BIG5是香港和臺灣地區使用的編碼集。它的範圍爲:高字節從0xA0到0xFE,低字節從0x40到0x7E,和0xA1到0xFE兩部分。判斷一個漢字是否是BIG5編碼,可以如上對字符的編碼範圍判斷即可。如何定位呢?那麼也想象所有編碼排列爲一個二維座標,縱座標是高字節,橫座標是低字節。這樣一行上的漢字個數:(0x7E-0x40+1)+(0xFE-0xA1+1)=157。那麼定位算法分兩塊,爲:

if 0x40<=ch2<=0x7E: #is big5 char
index=((ch1-0xA1)*157+(ch2-0x40))*2
elif 0xA1<=ch2<=0xFE: #is big5 char
index=((ch1-0xA1)*157+(ch2-0xA1+63))*2


對於第二塊,計算偏移量時因爲有兩塊數值,所以在計算後面一段值時,不要忘了前面還有一段值。0x7E-0x40+1=63。

編碼轉換
上面,我們已經可以得到GBK漢字和BIG5的字節位置。那麼就可以開始進行轉換了。對於轉換我原以爲有一個特別的算法,能夠按照兩種編碼的不同,簡單地通過計算就可以得出結果來,其實是不存在這種算法的。真正的做法是通過建立轉換碼錶文件實現的。即對於GBK碼錶,將原位置上的GBK漢字改成相應的BIG5漢字。對於BIG5碼錶,將原位置上的BIG5漢字改成相應的GBK漢字。這樣,由於原來漢字的位置沒有變,但編碼已經變成了想要轉換的編碼。通過計算出原漢字的位置,將轉換碼錶中對應漢字位置的字符取出來,這樣就完成了轉換(這就是爲什麼要進行編碼定位的原因)。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

GBK 漢字內碼擴展規範
  GBK 是又一個漢字編碼標準,全稱《漢字內碼擴展規範》(GBK),英文名稱 Chinese Internal Code Specification ,中華人民共和國全國信息技術標準化技術委員會 1995 年 12 月 1 日製訂,國家技術監督局標準化司、電子工業部科技與質量監督司 1995 年 12 月 15 日聯合以技監標函 [1995] 229 號文件的形式,將它確定爲技術規範指導性文件,發佈和實施。這一版的 GBK 規範爲 1.0 版。GB 即“國標”,K 是“擴展”的漢語拼音第一個字母。

  GBK 向下與 GB 2312 編碼兼容,向上支持 ISO 10646.1 國際標準,是前者向後者過渡過程中的一個承上啓下的標準。
  ISO 10646 是國際標準化組織 ISO 公佈的一個編碼標準,即 Universal Multilpe-Octet Coded Character Set(簡稱 UCS),大陸譯爲《通用多八位編碼字符集》,臺灣譯爲《廣用多八位元編碼字元集》,它與 Unicode 組織的 Unicode 編碼完全兼容。ISO 10646.1 是該標準的第一部分《體系結構與基本多文種平面》。我國 1993 年以 GB 13000.1 國家標準的形式予以認可(即 GB 13000.1 等同於 ISO 10646.1)。
  ISO 10646 是一個包括世界上各種語言的書面形式以及附加符號的編碼體系。其中的漢字部分稱爲“CJK 統一漢字”(C 指中國,J 指日本,K 指朝鮮)。而其中的中國部分,包括了源自中國大陸的 GB 2312、GB 12345、《現代漢語通用字表》等法定標準的漢字和符號,以及源自臺灣的 CNS 11643 標準中第 1、2 字面(基本等同於 BIG-5 編碼)、第 14 字面的漢字和符號。

  一、字彙
  GBK 規範收錄了 ISO 10646.1 中的全部 CJK 漢字和符號,並有所補充。具體包括:
  1. GB 2312 中的全部漢字、非漢字符號。
  2. GB 13000.1 中的其他 CJK 漢字。以上合計 20902 個 GB 化漢字。
  3. 《簡化字總表》中未收入 GB 13000.1 的 52 個漢字。
  4. 《康熙字典》及《辭海》中未收入 GB 13000.1 的 28 個部首及重要構件。
  5. 13 個漢字結構符。
  6. BIG-5 中未被 GB 2312 收入、但存在於 GB 13000.1 中的 139 個圖形符號。
  7. GB 12345 增補的 6 個拼音符號。
  8. 漢字“○”。
  9. GB 12345 增補的 19 個豎排標點符號(GB 12345 較 GB 2312 增補豎排標點符號 29 個,其中 10 個未被 GB 13000.1 收入,故 GBK 亦不收)。
  10. 從 GB 13000.1 的 CJK 兼容區挑選出的 21 個漢字。
  11. GB 13000.1 收入的 31 個 IBM OS/2 專用符號。

  二、碼位分配及順序
  GBK 亦採用雙字節表示,總體編碼範圍爲 8140-FEFE,首字節在 81-FE 之間,尾字節在 40-FE 之間,剔除 xx7F 一條線。總計 23940 個碼位,共收入 21886 個漢字和圖形符號,其中漢字(包括部首和構件)21003 個,圖形符號 883 個。
  全部編碼分爲三大部分:

  1. 漢字區。包括:
  a. GB 2312 漢字區。即 GBK/2: B0A1-F7FE。收錄 GB 2312 漢字 6763 個,按原順序排列。
  b. GB 13000.1 擴充漢字區。包括:
  (1) GBK/3: 8140-A0FE。收錄 GB 13000.1 中的 CJK 漢字 6080 個。
  (2) GBK/4: AA40-FEA0。收錄 CJK 漢字和增補的漢字 8160 個。CJK 漢字在前,按 UCS 代碼大小排列;增補的漢字(包括部首和構件)在後,按《康熙字典》的頁碼/字位排列。

  2. 圖形符號區。包括:
  a. GB 2312 非漢字符號區。即 GBK/1: A1A1-A9FE。其中除 GB 2312 的符號外,還有 10 個小寫羅馬數字和 GB 12345 增補的符號。計符號 717 個。
  b. GB 13000.1 擴充非漢字區。即 GBK/5: A840-A9A0。BIG-5 非漢字符號、結構符和“○”排列在此區。計符號 166 個。

  3. 用戶自定義區:分爲(1)(2)(3)三個小區。
  (1) AAA1-AFFE,碼位 564 個。
  (2) F8A1-FEFE,碼位 658 個。
  (3) A140-A7A0,碼位 672 個。
  第(3)區儘管對用戶開放,但限制使用,因爲不排除未來在此區域增補新字符的可能性。

  三、字形
  GBK 對字形作了如下的規定:
  1. 原則上與 GB 13000.1 G列(即源自中國大陸法定標準的漢字)下的字形/筆形保持一致。
  2. 在 CJK 漢字認同規則的總框架內,對所有的 GBK 編碼漢字實施“無重碼正形”(“GB 化”);即在不造成重碼的前提下,儘量採用中國新字形。
  3. 對於超出 CJK 漢字認同規則的、或認同規則尚未明確規定的漢字,在 GBK 碼位上暫安放舊字形。這樣,在許多情況下 GBK 收入了同一漢字的新舊兩種字形。
  4. 非漢字符號的字形,凡 GB 2312 已經包括的,與 GB 2312 保持一致;超出 GB 2312 的部分,與 GB 13000.1 保持一致。
  5. 帶聲調的拼音字母取半角形式。

 

GBK 代碼表(按代碼順序排列)

81-87 88-8F 90-97 98-9F A0-A7 A8-AF B0-B7 B8-BF
C0-C7 C8-CF D0-D7 D8-DF E0-E7 E8-EF F0-F7 F8-FE

 

--------------------------------------------------------------------------------


GBK 代碼表(按分類順序排列)

GBK/1: GB2312非漢字符號 A1-A9
GBK/2: GB2312 漢字 B0-B7 B8-BF C0-C7 C8-CF D0-D7
                  D8-DF E0-E7 E8-EF F0-F7
GBK/3: 擴充漢字    81-83 84-87 88-8B 8C-8F 90-93
                  94-97 98-9B 9C-A0
GBK/4: 擴充漢字    AA-AF B0-B7 B8-BF C0-C7 C8-CF
                  D0-D7 D8-DF E0-E7 E8-EF F0-F7 F8-FE
GBK/5: 擴充非漢字  A8-A9
用戶自定義區       (1) AA-AF (2) F8-FE (3) A1-A7

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章