Unicode 字符集與它的編碼方式

正式內容開始之前,我們先來了解一個基本概念,編碼字符集。

    編碼字符集:編碼字符集是一個字符集,它爲每一個字符分配一個唯一數字。Unicode 標準的核心是一個編碼字符集,字母“A”的編碼爲 004116 和字符“”的編碼爲 20AC16。Unicode 標準始終使用十六進制數字,而且在書寫時在前面加上前綴“U+”,所以“A”的編碼書寫爲“U+0041”。

 1 ASCII碼

    我們知道,在計算機內部,所有的信息最終都表示爲一個二進制的字符串。每一個二進制位(bit)有0和1兩種狀態,因此八個二進制位就可以組合出256種狀態,這被稱爲一個字節(byte)。也就是說,一個字節一共可以用來表示256種不同的狀態,每一個狀態對應一個符號,就是256個符號,從0000000到11111111。

   上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,做了統一規定。這被稱爲ASCII碼,一直沿用至今。

ASCII碼一共規定了128個字符的編碼(準確地說ASCII碼是一個編碼字符集),比如空格“SPACE”是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的1位統一規定爲0。後128個稱爲擴展ASCII碼,目前許多基於x86的系統都支持使用擴展ASCII碼。

   256個ASCII碼中的後128個擴展碼可定製用來表示特殊字符和非英語字符,GB2312就是利用這後面的128個擴展字符來表示漢字,[161,254]共94個字符來組成雙字節來表示簡體漢字字符表。

2 Unicode編碼字符集

   光是英語字符ASCII編碼字符集是夠了,但是如果算上世界上其他的語言的字符,ASCII碼顯然不夠了,於是Unicode編碼字符集應運而生。

   Unicode用數字0-0x10FFFF來映射這些字符,最多可以容納1114112個字符,或者說有1114112個碼位。碼位就是可以分配給字符的數字。UTF-8、UTF-16、UTF-32都是將數字轉  換到程序數據的編碼方案。

 

3 UTF-8

  http://zh.wikipedia.org/wiki/UTF-8

      Unicode編碼字符集只是統一定義了所有字符和它對應Unicode編碼值,而我們的程序中怎麼去存儲和讀取這個Unicode編碼值呢?顯然,你可以直接統一規定所有Unicode編碼值用四個字節來存儲。但是這樣的話,對於Unicode編碼字符集中的與ASCII碼錶對應的那部分字符(只需要一個字節來表示的Unicode編碼值)就有點浪費了。這樣,utf-8也就粉墨登場了。

   UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。

Unicode符號範圍 | UTF-8編碼方式
(十六進制) | (二進制)
--------------------+---------------------------------------------
0000 0000  ~  0000 007F | 0xxxxxxx               --------7bit
0000 0080  ~  0000 07FF | 110xxxxx 10xxxxxx    -------11bit
0000 0800  ~  0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx -----16bit
0001 0000  ~  0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ----21bit

UTF-8編碼規則:

1> 對於Unicode編碼值的二進制位數小於等於7的情況,用一個字節來表示這個Unicode編碼值,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。

2> 對於Unicode編碼值的二進制位數大於等於8並且小於等於11的情況,用兩個字節來表示,第一個字節的前位都設爲1,第+1位設爲0,第二個字節的前兩位設爲10。剩下的沒有提及的二進制位,全部填充這個符號對應的unicode碼。

3>  以此類推,對於需要n個utf-8 字節來表示的的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部填充這個符號對應的unicode碼。

 utf-8編碼長度最大爲四個字節,所以最多隻能表示Unicode編碼值的二進制數爲21位的Unicode字符

4. UTF-16   

   http://zh.wikipedia.org/wiki/UTF-16  

16進制編碼範圍 UTF-16表示方法(二進制) 10進制碼範圍 字節數量
U+0000---U+FFFF xxxxxxxx xxxxxxxx 0-65535 2
U+10000---U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111 4

UTF-16比起UTF-8,好處在於大部分字符都以固定長度的字節(2字節)儲存----0號平面(包含所有基本的字符)都在此表示範圍,但UTF-16卻無法相容於ASCII編碼.

 

   UTF-16編碼以16位無符號整數爲單位。我們把Unicode 編碼記作U。編碼規則如下:

  如果U<0x10000,U的UTF-16編碼就是U對應的16位無符號整數(爲書寫簡便,下文將16位無符號整數記作WORD)。

  如果U≥0x10000,我們先計算U'=U-0x10000,然後將U'寫成二進制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16編碼(二進制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

  爲什麼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。

  按照上述規則,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編碼範圍應該是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都作爲專用區,所以0xDB80到0xDBFF之間的保留碼位被稱作高位專用替代。

 

5. 標準Unicode編碼表分析

Unicode字符平面映射:

http://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%B9%B3%E9%9D%A2%E6%98%A0%E5%B0%84

完整的Unicode編碼表可見鏈接:http://zh.wikibooks.org/wiki/Unicode 

目前的 Unicode 字符分爲 17 組編排, 每組稱爲平面(Plane),而每平面擁有65536(即 216)個代碼點。然而目前只有少數平面被使用。
上述的Unicode編碼表鏈接中只列出了少數幾個已經被使用的平面。

U+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
0010 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
0020 SP ! " # $ % & ' ( ) * + , - . /
0030 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0040 @ A B C D E F G H I J K L M N O
0050 P Q R S T U V W X Y Z [ \ ] ^ _

表分爲橫豎兩列,相當於x和y確定唯一的Unicode的值(十六進制)。如:
ESC字符由x座標0010和y座標B確定,那麼它的Unicode編碼值就是0010 + B =  001B
表就是這麼讀的啦。

6. UTF-8和UTF-16字節序的問題

  http://zh.wikipedia.org/wiki/UTF-8

  http://zh.wikipedia.org/wiki/UTF-16

 網上查了下,對於這二者的字節序的原因很表面。字節序的介紹如下:
    UTF-8以字節爲編碼單元,沒有字節序的問題。UTF-16以兩個字節爲編碼單元,所以有字節序的問題。
    那爲什麼以字節爲編碼單元的utf-8就沒有字節序的問題呢?它也可以有三個或四個字節組成呀。我想很多人都會這麼問的,也包括我自己。讓我們再回頭來看看字節序是什麼東西,對於單個字節長度的變量類型來說,它是沒有字節問題的。對於多字節長度的變量類型纔有字節問題。

   如果牽涉到在多個CPU之間傳輸數字問題.如通過網絡,文件和總線傳輸數字的話.必須要考慮到數字字節序問題。網絡傳輸時,不同字節序的平臺進行數據傳輸時,在發送數據之前都必須轉換成爲網絡字節序後再進行傳輸,即統一用的網絡字節序(即大頭)來傳輸。接收方根據自己cpu的字節序將網絡字節序轉換爲本地字節序,這樣一來,接收方按再按照自己的字節序去讀取這個數據時和發送方讀取的是一樣的。因此,對於網絡傳輸,網絡傳輸這一層面的字節序問題已經解決,Unicode編碼層面的字節序問題與下面的文件存儲中的一樣。總線,這個是同一平臺多cpu(各cpu的字節序不一樣)之間的數據交換的情況,我想我們基本上是接觸不到這種情況了,不做研究之列。剩下就是個通過文件傳輸數據的情況,即一個平臺上的文件在另外一個不同字節序的平臺上打開。這種情況怎麼考慮字節序的問題呢?

   再回過頭來仔細分析"UTF-8以字節爲編碼單元,沒有字節序的問題。UTF-16以兩個字節爲編碼單元,所以有字節序的問題。"這句話的含義了。什麼是編碼單元?Unicode只是一個符號集,根據UTF-8或是UTF-16可以計算出某個符號對應的的唯一的二進制代碼,但是這個二進制的代碼具體怎麼存儲的呢,這個編碼規則中都沒有指定。但是UTF-8指定了,存儲這個二進制代碼時,必須按字節爲單元來讀和存儲;UTF-16指定了,存儲這個二進制代碼時,必須按兩個字節爲單元來讀和存儲。這個時候我們應該知道其實編碼單元,就是某種編碼方式中,最小讀取和存儲其二進制數值的單元。呵呵,明白這個很重要哦。

   無論是UTF-8或UTF-16,其最終的二進制編碼長度都有可能是大於等於兩個字節的。但是UTF-8是按照字節爲編碼單元,所以存儲時是按照其編碼順序(二進制數值從左到右)的順序來存儲的。所以讀取文件時,按照文件中存儲順序一個字節一個字節讀取就能得到和存儲時一樣的二進制編碼了,也就不需要考慮字節序了。而UTF-16是按照兩個字節爲編碼單元的,它的二進制編碼只有兩個字節或四個字節這兩種長度。所以存儲時,按照編碼順序(二進制數值從左到右),一次存儲兩個字節到文件,直到全部存完。這樣兩個字節與其下一個兩個字節之間的存儲順序也是與二進制編碼順序一致的。但是作爲編碼單元的這兩個字節本身的順序怎麼放呢?UTF-16是沒有指定的,你可以根據自己的喜好人爲地按大頭來存放這兩個字節,也可以按照小頭來存放。所以你按照UTF-16編碼後存儲的文件,別人去讀你這個文件中的內容時,是按照大頭還是小頭去讀這個編碼單元呢?

  如果還有那麼一點暈的話,我再舉個例子了:
假設要將 U+64321 (16進位) 轉成 UTF-16 編碼. 因爲它超過 U+FFFF, 所以他必須編譯成32位(4個byte)的格式,如下所示:11
V  = 0x64321
Vx = V - 0x10000
   = 0x54321
   = 0101 0100 0011 0010 0001

Vh = 01 0101 0000 // Vx 的高位部份的 10 bits
Vl = 11 0010 0001 // Vx 的低位部份的 10 bits
w1 = 0xD800 //結果的前16位元初始值
w2 = 0xDC00 //結果的後16位元初始值

w1 = w1 | Vh
   = 1101 1000 0000 0000
   |        01 0101 0000
   = 1101 1001 0101 0000
   = 0xD950

w2 = w2 | Vl
   = 1101 1100 0000 0000
   |        11 0010 0001
   = 1101 1111 0010 0001
   = 0xDF21

所以這個字 U+64321 最後正確的 UTF-16 二進制編碼應該是:
1101 1001 0101 00001101 1111 0010 0001
綠色部分爲高16位,紅色爲低16位。
因爲UTF-16 的編碼單元爲兩個字節,所以存儲時,按照二進制編碼順序,先存儲高位的兩個字節1101 10010101 0000 ,然後再存儲地位的兩個字節1101 1111 0010 0001。
但是兩個字節之間的順序是什麼呢?順序就是先存儲兩個字節中的"低地址"的那個字節,然後再存儲高地址的那個字節。

如果是小頭順序(最低字節在最低位,最高字節在最高位),則"低地址"中存儲的是低字節。所以,在高位的兩個字節中,先存儲最低位的字節50,再存儲最高位的字節D9,即十六進制格式存儲爲50D9 ;同理,低位的兩個字節的存儲順序就是21DF。四個字節連起來,存儲內容就是50D9 21DF。
如果是大頭順序(最高字節在地址最低位,最低字節在地址最高位),則"低地址"中存儲的是高字節。所以,高位連個字節先存儲最低位的字節D9,再存儲最高爲的字節50 ----D950
同理,低位的兩個字節的存儲順序就是DF21。四個字節連起來,存儲內容就是D950 DF21。
 

 7. UTF-8和UTF-16字節序的問題的對應解決方案   
   經過上述的分析,相信大家應該清楚了字節序的問題了。那麼如何讓別的程序讀取你寫的文件時知道你存儲時的字節序呢?
   Unicode規範中定義,每一個文件內容的最前面加入一個表示編碼順序的字符,這個字符的名字叫做”零寬度非換行空格“(ZERO WIDTH NO-BREAK SPACE),這個字符對應的Unicode字符的編碼值爲FEFF。所以對於UTF-16,如果你寫的時候使用小頭順序,則這個字符在文件中的存儲順序爲FFFE;如果是大頭順序,則這個字符的存儲順序爲FEFF。
   UTF -8本來是與字節序無關的,不需要指定編碼字節序,但是可以用BOM(Byte Order Mark)來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF,因爲是以字節爲編碼單元存儲的,所以這個字符文件中對應的存儲格式與編碼格式一樣。根據這一點,我們知道如果文件內容以EF BB BF開頭,就知道這是UTF-8編碼了。
 8. 不同編碼方式的轉換
   windows下,記事本中,支持將文件中的內容以不同的編碼方式存儲。
   ANSI是記事本中默認的編碼方式-----對於英文文件是ASCII編碼,對於簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會採用Big5碼)。
  記事本也支持utf-8格式的,通過依次採用ANSI 和 UTF-8編碼方式保存,我們可以看到這兩種編碼方式之間的轉換。用文本編輯軟件UltraEdit中的十六進制格式,觀察該文件的不同編碼方式對應的值。

9 讀寫文本文件時,程序時如何判斷文件的編碼格式

 1. 首先,程序通過通過判斷文件頭幾個字節來判斷文件的編碼格式(BOM字節)

ANSI :         無格式定義;
Unicode :      前兩個字節爲 FFFE ;
Unicode big endian : 前兩字節爲 FEFF ; 
UTF-8 :        前兩字節爲 EFBB ;

UTF-16 big endian的BOM: FF FE;   
UTF-16 little endian的BOM: FE FF;

2. BOM不存在的情況判定。

    UTF-8的判定,根據內容判定

        UTF-8的編碼規則:

                   字符字節長度        標誌字節        值

                     一字節長            0XXXXXXX

                     兩字節長            110XXXXX  10XXXXXX

                     三字節長            1110XXXX  10XXXXXX 10XXXXXX 

                     四字節長            11110XXX  10XXXXXX 10XXXXXX 10XXXXXX 

        **標誌字節判定用到的數據**

        定義數組btHead 長度爲4保存用來判定標誌字節的十進制數值: 0,192, 224, 240

        定義數組btBitAndValue 長度爲4保存用來得到標誌字節長度的十進制數值:128: 224, 240, 248

        **值判定用到的數據**

        定義變量btValueHead用來保存值得標誌所對應的十進制數值:128

        定義變量btFixValueAnd 保存用來取得值的標誌的十進制數值:192

 

        a.以字節方式讀取文件中內容保存到字節數組中

        b.對a中讀取的文件內容做loop操作。

           首先對當前的字節分別與btBitAndValue中的四個值進行位與操作,每次得到的值與btHead中的值比較,找到相等的值時可以根據當前的   值來判定字符的字節長度L。並執行下一個循環,在跳過b的操作次數(L - 1)次時在執行b操作

        c.取得值的標誌。 將此致的值與btFixValueAnd進行位與操作,將取得的值與btValueHead進行比較,如果相等則對下一個字節繼續執行c操作,直到執行的次數是L-1次。如果不相等則說明不是UTF-8編碼格式。

   UTF-16的判定與UTF-8的判定類似只要知道編碼規則就可以。

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