UTF-8解碼

來源:互聯網
上載者:User

要想瞭解UTF-8編碼規則,請參考我的文章:http://blog.csdn.net/sheismylife/article/details/8570015

在我的另一篇文章"UTF-8編碼實測" http://blog.csdn.net/sheismylife/article/details/8571726 中,我使用了boost::locale庫的代碼來解碼UTF-8. 現在來仔細研究一下解碼的演算法:

如何分辨leading byte和continuation bytes呢?關鍵在於任何一個continuation byte都以10開始。下面的函數可以協助判斷是否為continuation byte:

bool is_trail(char ci) {  unsigned char c = ci;  return (c & 0xC0) == 0x80;}

因為0xC0二進位格式是1100 0000,和c按位與後也就是低六位全部設定為0,進保留c的高兩位.

而0x80二進位格式是1000 0000, 如果兩者相等,說明c的高兩位是10,因此c是continuation byte。返回true。

有了這個函數,判斷一個位元組是否為leading byte也很簡單:

bool is_lead(char ci) {  return !is_trail(ci);}

再看一下函數trail_length, 該函數通過分析一個leading byte來確定continuation bytes的長度。

int trail_length(char ci) {  unsigned char c = ci;  if(c < 128)    return 0;  if(BOOST_LOCALE_UNLIKELY(c < 194))    return -1;  if(c < 224)    return 1;  if(c < 240)    return 2;  if(BOOST_LOCALE_LIKELY(c <=244))    return 3;  return -1;}

如果c < 128, 說明就是一個ASCII字元,用一個位元組表示。因此conitunuation bytes長度為0,utf-8編碼總長度為1.

因為11011111就是223,只要c在區間[128, 224), 說明continuation bytes長度為1,utf-8編碼總長度為2.

繼續推理,11101111等於239, 因此在[224, 240)區間的,continuation bytes長度為2,utf-8編碼總長度為3.

11110111等於247, 因此在[240, 248)區間的,continuation bytes長度為3,utf-8編碼總長度為4. 但是這裡限定的區間實際上是[240, 244] 不明白為什麼,可能還有什麼編碼規則我不清楚。

現在來看一下utf.hpp中的代碼,http://www.boost.org/doc/libs/1_53_0/libs/locale/doc/html/utf_8hpp_source.html

00192         template<typename Iterator>00193         static code_point decode(Iterator &p,Iterator e)00194         {00195             if(BOOST_LOCALE_UNLIKELY(p==e))00196                 return incomplete;00197 00198             unsigned char lead = *p++;00199 00200             // First byte is fully validated here00201             int trail_size = trail_length(lead);00202 00203             if(BOOST_LOCALE_UNLIKELY(trail_size < 0))00204                 return illegal;00205 00206             //00207             // Ok as only ASCII may be of size = 000208             // also optimize for ASCII text00209             //00210             if(trail_size == 0)00211                 return lead;00212             00213             code_point c = lead & ((1<<(6-trail_size))-1);00214 00215             // Read the rest00216             unsigned char tmp;00217             switch(trail_size) {00218             case 3:00219                 if(BOOST_LOCALE_UNLIKELY(p==e))00220                     return incomplete;00221                 tmp = *p++;00222                 if (!is_trail(tmp))00223                     return illegal;00224                 c = (c << 6) | ( tmp & 0x3F);00225             case 2:00226                 if(BOOST_LOCALE_UNLIKELY(p==e))00227                     return incomplete;00228                 tmp = *p++;00229                 if (!is_trail(tmp))00230                     return illegal;00231                 c = (c << 6) | ( tmp & 0x3F);00232             case 1:00233                 if(BOOST_LOCALE_UNLIKELY(p==e))00234                     return incomplete;00235                 tmp = *p++;00236                 if (!is_trail(tmp))00237                     return illegal;00238                 c = (c << 6) | ( tmp & 0x3F);00239             }00240 00241             // Check code point validity: no surrogates and00242             // valid range00243             if(BOOST_LOCALE_UNLIKELY(!is_valid_codepoint(c)))00244                 return illegal;00245 00246             // make sure it is the most compact representation00247             if(BOOST_LOCALE_UNLIKELY(width(c)!=trail_size + 1))00248                 return illegal;00249 00250             return c;00251 00252         }

decode函數負責從字串中解析一個utf-8編碼(可能包含1-4位元組),返回對應的code point,一個uint32_t的整數。

總是假定第一個位元組是leading byte,然後用trail_length擷取continuation bytes的長度,如果為0,說明就是ASCII字元,直接返回。

210行到250行處理的都是非ASCII字元。先注意switch/case用法,這裡沒有break語句,也就是說,如果trail_size為3,會先執行case 3:裡面的語句,然後再依次執行case 2和 case 1(不需要匹配)。這是來自switch/case的特殊文法。參考:http://msdn.microsoft.com/en-us/library/k0t5wee3.aspx

當然用迴圈能更好的表達,但是不知道Artyom為什麼選擇這種寫法?效能更高嗎?至少有一條,如果有代碼掃描工具的話,會認為這裡三段代碼重複,會警示告資訊。:)

現在再看一下之前UTF-8編碼一文中引用的wiki的例子:

現在看一個來自wiki的例子示範如何對字元€進行UTF-8編碼:step 1:擷取€的Unicode code point,是0xU+20ACstep 2:0xU+20AC範圍在U+07FF和U+FFFF之間,因此用三位元組表示。step 3:0xU+20AC的二進位碼是:10000010101100,14位長,要想表示3位元組編碼,必須湊成16 bits.因此高位補上兩個0,變成2位元組16位長:0010000010101100,我下面稱為數值串。step 4: 根據規則,添加一個leading byte,開頭是1110,那麼這個leading byte還有4個bit需要填充,從數值串高位取4個bit來,leading byte變成了:11100010,而數值串值為000010101100step 5: 第一個continuation byte高位應該是10,還缺少6 bits,從數值串中按高位取6 bit,這樣第一個continuation byte為:10000010,而數值串變為101100step 6: 第二個continuation byte高位也應該是10,還缺少6 bits, 從數值串取6 bits,這樣第二個continuation byte為:10101100最終編碼形成的三位元組:11100010 10000010 10101100寫成16進位就是0xE282AC

解碼也就是編碼的逆過程,從leading byte中抽取低4位,從兩個continuation bytes中都抽取低6位,拼接成16bits的整數,然後轉換類型變成uint32_t的整數。

下面的代碼其實就是取出低四位:

code_point c = lead & ((1<<(6-trail_size))-1);

這是個很好的技巧,總結一下可以寫成一個函數, 函數接受兩個參數,一個是要提取bit的x,一個是提取的位元。

uint8_t GetLowNBit(uint8_t x, uint8_t n) {  return x & ((1<<n)-1);}

為什麼這裡用6-trail_size,純粹是觀察出來的規律。

2位元組utf-8編碼時,leading byte以110開頭,因此要提取低5位元值, 6-trail_size=6-1=5,剛好能夠提取低5位。

3位元組utf-8編碼,leading byte以1110開頭,因此要提取低4位元值,6-trail_size=6-2=4.

4位元組utf-8編碼,leading byte以11110開頭,因此要提取低3位元值,6-trail_size=6-3=3.

所以這裡用6,Artyom的觀察力很敏銳。

提取出leading byte的低位元據後,現在要提取continuation bytes的低位元據,也就是那個switch/case的功能。

這就很簡單了,tmp & 0x3F就是取出低6位元據,因為continuation byte永遠都是10開頭。每次取出低6位後,將c左移6位然後按位或,就達到合并bit成新的整數的目的。

前面已經解釋過swtich/case在without break的用法。這裡就會運行兩次,取出後面兩個continuation bytes的低6位元據,併合並。

UTF-8解碼演算法分析完成。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.