gbk/utf8/unicode編碼轉換,不依賴任何系統API自己實現與平臺無關的轉換算法

原文鏈接(http://blog.csdn.net/coollofty/article/details/8058859)


GBK、UTF8、Unicode,這三種編碼是一般程序開發,或者各種應用中最常見的三種編碼方式了,還不知道基本概念的趕快請教搜索引擎自己科普一下。


本文的目的不是來講述什麼是GBK編碼,什麼是UTF8編碼,他們的編碼規則是怎樣的這一類的基本概念的文章。本文要講述的是如何沒有系統API輔助的情況下,如何最簡單的快速實現這三種編碼之間的轉換。也許你會說,這有什麼意義呢?Windows上、Linux上,我們都有系統API可以進行轉換,一個API搞定了。退一步說,還有libiconv可以墊底,何必自己搞?話是這麼說沒錯,但這不等於所有的系統上都有這些編碼轉換的API,比如Android的NDK開發的時候,編碼轉換就是一個討厭的問題,雖然Android自身有帶這樣的API,但是他沒有提供給我們使用,網上的解決方案都存在着或多或少的問題,不是性能不成(通過Java轉調,效率太低),就是自己去dl_open icu庫,但是那個函數的名稱又很蛋疼。而我們只是需要轉換這麼3種覺編碼而已,搞出iconv來一下就是好幾MB的庫,真覺得沒有必要。總之解決得都很不爽。咱是C++程序員,索性,本着底層開發的原則,自己搞一套吧。


這三種編碼的轉換,UTF8與Unicode之間是很簡單的(這裏的Unicode指的是UCS-2),直接貼代碼:

  1. //參數1是UTF8字符串當前位置指針,這裏必須要是指針,因爲必須要通過第1個字符進行判斷才知道一個完整的字符的編碼要向後取多少個字符  
  2. //參數2是返回的UCS-2編碼的Unicode字符  
  3. inline int UTF82UnicodeOne(const char* utf8, wchar_t& wch)  
  4. {  
  5.     //首字符的Ascii碼大於0xC0才需要向後判斷,否則,就肯定是單個ANSI字符了  
  6.     unsigned char firstCh = utf8[0];  
  7.     if (firstCh >= 0xC0)  
  8.     {  
  9.         //根據首字符的高位判斷這是幾個字母的UTF8編碼  
  10.         int afters, code;  
  11.         if ((firstCh & 0xE0) == 0xC0)  
  12.         {  
  13.             afters = 2;  
  14.             code = firstCh & 0x1F;  
  15.         }  
  16.         else if ((firstCh & 0xF0) == 0xE0)  
  17.         {  
  18.             afters = 3;  
  19.             code = firstCh & 0xF;  
  20.         }  
  21.         else if ((firstCh & 0xF8) == 0xF0)  
  22.         {  
  23.             afters = 4;  
  24.             code = firstCh & 0x7;  
  25.         }  
  26.         else if ((firstCh & 0xFC) == 0xF8)  
  27.         {  
  28.             afters = 5;  
  29.             code = firstCh & 0x3;  
  30.         }  
  31.         else if ((firstCh & 0xFE) == 0xFC)  
  32.         {  
  33.             afters = 6;  
  34.             code = firstCh & 0x1;  
  35.         }  
  36.         else  
  37.         {  
  38.             wch = firstCh;  
  39.             return 1;  
  40.         }  
  41.   
  42.         //知道了字節數量之後,還需要向後檢查一下,如果檢查失敗,就簡單的認爲此UTF8編碼有問題,或者不是UTF8編碼,於是當成一個ANSI來返回處理  
  43.         for(int k = 1; k < afters; ++ k)  
  44.         {  
  45.             if ((utf8[k] & 0xC0) != 0x80)  
  46.             {  
  47.                 //判斷失敗,不符合UTF8編碼的規則,直接當成一個ANSI字符返回  
  48.                 wch = firstCh;  
  49.                 return 1;  
  50.             }  
  51.   
  52.             code <<= 6;  
  53.             code |= (unsigned char)utf8[k] & 0x3F;  
  54.         }  
  55.   
  56.         wch = code;  
  57.         return afters;  
  58.     }  
  59.     else  
  60.     {  
  61.         wch = firstCh;  
  62.     }  
  63.   
  64.     return 1;  
  65. }  
有了這個函數,那麼轉換一整個UTF8字符串,就是很簡單的一件事情了,下面直接給出了最短的實現:
  1. //參數1是UTF8編碼的字符串  
  2. //參數2是輸出的UCS-2的Unicode字符串  
  3. //參數3是參數1字符串的長度  
  4. //使用的時候需要注意參數2所指向的內存塊足夠用。其實安全的辦法是判斷一下pUniBuf是否爲NULL,如果爲NULL則只統計輸出長度不寫pUniBuf,這樣  
  5. //通過兩次函數調用就可以計算出實際所需要的Unicode緩存輸出長度。當然,更簡單的思路是:無論如何轉換,UTF8的字符數量不可能比Unicode少,所  
  6. //以可以簡單的按照sizeof(wchar_t) * utf8Leng來分配pUniBuf的內存……  
  7. int UTF82Unicode(const char* utf8Buf, wchar_t *pUniBuf, int utf8Leng)  
  8. {     
  9.     int i = 0, count = 0;  
  10.     while(i < utf8Leng)  
  11.     {  
  12.         i += UTF82UnicodeOne(utf8Buf + i, pUniBuf[count]);  
  13.         count ++;  
  14.     }  
  15.   
  16.     return count;  
  17. }  

搞定了UTF-8轉Unicode之後,反過來搞定Unicode轉UTF8也是很容易的,下面直接給出單個Unicode轉UTF8編碼的函數:

  1. inline int Unicode2UTF8(unsigned wchar, char *utf8)  
  2. {  
  3.     int len = 0;  
  4.     if (wchar < 0xC0)  
  5.     {   
  6.         utf8[len ++] = (char)wchar;  
  7.     }  
  8.     else if (wchar < 0x800)  
  9.     {  
  10.         utf8[len ++] = 0xc0 | (wchar >> 6);  
  11.         utf8[len ++] = 0x80 | (wchar & 0x3f);  
  12.     }  
  13.     else if (wchar < 0x10000)  
  14.     {  
  15.         utf8[len ++] = 0xe0 | (wchar >> 12);  
  16.         utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);  
  17.         utf8[len ++] = 0x80 | (wchar & 0x3f);  
  18.     }  
  19.     else if (wchar < 0x200000)   
  20.     {  
  21.         utf8[len ++] = 0xf0 | ((int)wchar >> 18);  
  22.         utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);  
  23.         utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);  
  24.         utf8[len ++] = 0x80 | (wchar & 0x3f);  
  25.     }  
  26.     else if (wchar < 0x4000000)  
  27.     {  
  28.         utf8[len ++] = 0xf8 | ((int)wchar >> 24);  
  29.         utf8[len ++] = 0x80 | ((wchar >> 18) & 0x3f);  
  30.         utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);  
  31.         utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);  
  32.         utf8[len ++] = 0x80 | (wchar & 0x3f);  
  33.     }  
  34.     else if (wchar < 0x80000000)  
  35.     {  
  36.         utf8[len ++] = 0xfc | ((int)wchar >> 30);  
  37.         utf8[len ++] = 0x80 | ((wchar >> 24) & 0x3f);  
  38.         utf8[len ++] = 0x80 | ((wchar >> 18) & 0x3f);  
  39.         utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);  
  40.         utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);  
  41.         utf8[len ++] = 0x80 | (wchar & 0x3f);  
  42.     }  
  43.   
  44.     return len;  
  45. }  
參數1是一個Unicode編碼,參數2爲輸出的UTF8編碼。這裏參數1使用了unsigned類型,不是wchar_t類型,僅僅是按照UTF8編碼規則可能達到的最大範圍來寫而已。一個Unicode編碼會輸出多少個char字符,函數的返回值明確的給出了。所以原則上,參數2至少應該是一個char buf[6]的數組。至於一個Unicode字符串轉UTF8字符串的轉換代碼,這裏就不貼了,實在是簡單,不浪費文字了。


現在開始就是不好做的GBK轉Unicode了。衆所周知GBK編碼使用其實非常廣泛,絕大多數的文本編輯軟件,像Editplus,WIndows自帶的記事本等等,如果你不設置其編碼,那麼默認基本上就是GBK,這個東西使用太廣泛了,廣泛得我們都快忘了他了。在Windows的API裏,MultiByteToWideChar轉換的時候,參數1爲CP936,意思就是輸入的多字節字符串爲GBK編碼,我們可以簡單的認爲這個GBK和CP936字符串是一回事(當然其實不能絕對的這樣講,畢竟不是一個組織定下來的東西,我們只是從技術上簡單的這樣認爲他們是一樣的就可以了)。而這個GBK轉Unicode編碼,難就難在,不像UTF8轉Unicode一個算法就可以搞定了,GBK轉Unicode只能通過查表來實現,Unicode轉GBK也一樣,只能通過查表來實現。


這下問題就複雜了,因爲要查表就意味着GBK與Unicode編碼之間雖然是一一對應關係,但不是簡單的高位乘以多少加上低位乘以多少就能算出來的。更糟糕的是GBK與Unicode之間並不是連續的編碼,中間總有空碼,而且這個空碼的情況,還不是特別的有規律。


不過好在微軟公開了CP936與自家UCS-2的對照文本,基本這個文本,就可以很容易的看到每一個GBK編碼的字符與Unicode碼之間的對應關係了。這就好辦了,我寫了一個小程序,將這個文本文件讀入,解析,然後輸出一段C語言代碼,其實就是一個大數組,在轉換的時候,拿GBK碼去這個數組裏,就可以得到Unicode編碼了。而且這個數組也不算大,因爲最多隻有65536個編碼,也就是64*2=128Kb的Table。反過來Unicode到GBK,也同樣是一個short型的table,也是128Kb。這個程序增長量還是可以接受的。如果覺得不爽的話, 也可以寫在文本文件裏,第一次運行的時候將文本載到內存裏…………不過我覺得這樣其實沒有什麼區別。


好了,廢話少說了,下面是GBK轉Unicode和Unicode轉GBK編碼的查表函數,文章的最後面是兩個我導出的C語言代碼的表文件鏈接,在下面的這兩個函數之前用#include包進來就可以了。

  1. //參數1是輸入的Unicode字符串  
  2. //參數2是輸出的GBK字符串  
  3. //參數3是輸入字符串的長度  
  4. //返回值是輸出GBK字符串的長度  
  5. int Unicode2GBK(const wchar_t* wchar, char *gbkBuf, int wcharLeng)  
  6. {  
  7.     int outLeng = 0;  
  8.     uchar* pWrite = (uchar*)gbkBuf;  
  9.     for(int i = 0; i < wcharLeng; ++ i)  
  10.     {  
  11.         wchar_t c = wchar[i];  
  12.         if (c <= 0x7F)  
  13.         {  
  14.             //小於0x7F,這是一個ANSI碼,所以不用查表了  
  15.             outLeng ++;  
  16.             *pWrite ++ = c;  
  17.         }  
  18.         else if (c == 0x20AC)  
  19.         {  
  20.             //一個特殊字符,沒有編進表裏,所以在這裏單獨處理了  
  21.             *pWrite ++ = 0x80;  
  22.             outLeng ++;  
  23.         }  
  24.         else  
  25.         {  
  26.             //剩下的,就需要查表了,減去128,直接去表裏查。Unicode轉GBK的好處是隻有一張線性表,一次就可以查到  
  27.             unsigned short ss = unicode2gbkTable[c - 128];  
  28.             *pWrite ++ = ss >> 8;  
  29.             *pWrite ++ = ss & 0xFF;  
  30.             outLeng += 2;  
  31.         }  
  32.     }  
  33.   
  34.     return outLeng;  
  35. }  
  36.   
  37. //參數1是輸入的GBK字符串  
  38. //參數2是輸出的Unicode字符串  
  39. //參數3是輸入字符串的長度  
  40. //返回值是輸出Unicode字符串的長度  
  41. int GBK2Unicode(const char* gbkBuf, wchar_t *pszBuf, int gbkLeng)  
  42. {  
  43.     int outLeng = 0;  
  44.     const uchar* pSrc = (const uchar*)gbkBuf;  
  45.     wchar_t* pWrite = pszBuf;  
  46.     for(int i = 0; i < gbkLeng; ++ i)  
  47.     {  
  48.         uchar ch = pSrc[i];  
  49.         if (ch <= 0x7F)  
  50.         {  
  51.             //ANSI字符  
  52.             *pWrite ++ = ch;  
  53.         }  
  54.         else if (ch == 0x80)  
  55.         {  
  56.             //特殊字符  
  57.             *pWrite ++ = 0x20AC;  
  58.             ++ i;  
  59.         }  
  60.         else  
  61.         {  
  62.             //剩下的,就需要查表了,高位減128,得到這一段字符的表。整個Unicode轉GBK一共分了幾十個表,gbk2unicodeTables則記錄了這些表的地址  
  63.             //所以先要按高位得到表地址,再用低位去該表裏查找字符  
  64.             ++ i;  
  65.             ch -= 0x81;  
  66.             if (ch < sizeof(gbk2unicodeTables) / sizeof(gbk2unicodeTables[0]))  
  67.             {  
  68.                 const unsigned short* pTable = gbk2unicodeTables[ch];  
  69.                 ch = pSrc[i];  
  70.                 if (ch < 255)  
  71.                     *pWrite ++ = pTable[ch - 0x40];  
  72.                 else  
  73.                     *pWrite ++ = 0;  
  74.             }  
  75.             else  
  76.             {  
  77.                 *pWrite ++ = 0;  
  78.             }  
  79.         }  
  80.   
  81.         outLeng ++;  
  82.     }  
  83.   
  84.     return outLeng;  
  85. }  

現在搞定了UTF8和Unicode之間的互相轉換,也搞定了GBK和Unicode之間的互相轉換,那麼GBK和UTF8之間呢?呵呵,學學Windows的API,先將GBK轉成Unicode,然後將Unicode再轉成UTF-8,反過來亦然。


GBK和Unicode互轉的字符碼錶文件

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