終于到討論編碼轉(zhuǎn)換這一步了。
先來看Unicode和UTF-8之間的轉(zhuǎn)換,前面我們說過Unicode和UTF-8的字符是一一對應(yīng)的。他們的對應(yīng)規(guī)則如下:
Unicode和UTF-8之間的轉(zhuǎn)換關(guān)系表
UCS-4編碼UTF-8字節(jié)流
U+00000000 – U+0000007F0xxxxxxx
U+00000080 – U+000007FF110xxxxx 10xxxxxx
U+00000800 – U+0000FFFF1110xxxx 10xxxxxx 10xxxxxx
U+00010000 – U+001FFFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U+00200000 – U+03FFFFFF111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U+04000000 – U+7FFFFFFF1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
以上表格摘自維基百科,該表格記錄了UCS-4 與UTF-8的對應(yīng)關(guān)系。上面的x表示我們可以編碼的位。這個(gè)表記錄的內(nèi)容太多,我們平常使用只需要前三行,也就是UCS-2的表示范圍。這基本可以表示我們國際上通用的所有文字和特殊符號(hào)了。
再來解釋一下UTF-8編碼字節(jié)含義:
對于UTF-8編碼中的任意字節(jié)B,如果B的第一位為0,則B為ASCII碼,并且B獨(dú)立的表示一個(gè)字符;
如果B的第一位為1,第二位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的一個(gè)字節(jié),并且不為字符的第一個(gè)字節(jié)編碼;
如果B的前兩位為1,第三位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的第一個(gè)字節(jié),并且該字符由兩個(gè)字節(jié)表示;
如果B的前三位為1,第四位為0,則B為一個(gè)非ASCII字符(該字符由多個(gè)字節(jié)表示)中的第一個(gè)字節(jié),并且該字符由三個(gè)字節(jié)表示;
有了這層對應(yīng)關(guān)系,Unicode到utf-8的轉(zhuǎn)化代碼就不難實(shí)現(xiàn)了,以下是我用c實(shí)現(xiàn)的,經(jīng)多年線上驗(yàn)證沒有問題。
typedef char T_GB;typedef unsigned short T_UC;typedef unsigned char T_UTF8;/*! * \brief UCS-2編碼文本轉(zhuǎn)換為UTF-8編碼文本 * \param[in] puc: UCS-2字符串的地址 * \param[in] nuclen: UCS-2字符串的長度 * \param[out] putf8: 輸出的UTF-8字符串的地址 * \param[in] nutf8len: 最大可以允許的UTF-8字符串的長度,如果nutf8len<nuclen*3,可能會(huì)出現(xiàn)部分字符被截?cái)?* \return int 轉(zhuǎn)換后的字符長度 */int uc2utf8(const T_UC* puc, size_t nuclen, T_UTF8* putf8, size_t nutf8len){ const T_UC* ucbpos = puc; const T_UC* ucepos = puc+nuclen; T_UTF8* utf8bpos = putf8; T_UTF8* utf8epos = putf8+nutf8len; while (ucbpos< ucepos && utf8bpos<utf8epos) { if (*ucbpos < 0x80) { *utf8bpos++ = *ucbpos++; } else if (*ucbpos < 0x800) { if (utf8epos-utf8bpos < 2) { break; } *utf8bpos++ = ((*ucbpos&0x7C0)>>6) | 0xC0; *utf8bpos++ = (*ucbpos++ & 0x3F) | 0x80; } else { if (utf8epos-utf8bpos < 3) { break; } *utf8bpos++ = ((*ucbpos&0xF000)>>12) | 0xE0; *utf8bpos++ = ((*ucbpos&0x0FC0)>>6) | 0x80; *utf8bpos++ = ((*ucbpos++&0x3F)) | 0x80; } } return (utf8bpos-putf8);}/*! * \brief UTF-8編碼文本轉(zhuǎn)換為UCS-2編碼文本 * \param[in] putf8: UTF-8字符串的地址 * \param[in] nutf8len: UTF-8字符串的長度 * \param[out] puc: 輸出的UCS-2字符串的地址 * \param[in] nuclen: 最大可以允許的UCS-2字符串的長度,如果nuclen<nutf8len,可能會(huì)出現(xiàn)部分字符被截?cái)?* \return int 轉(zhuǎn)換后的字符長度 */int utf8uc2(const T_UTF8* putf8, size_t nutf8len, T_UC* puc, size_t nuclen){ const T_UTF8 * utf8bpos = putf8; const T_UTF8 * utf8epos = putf8 + nutf8len; T_UC * ucbpos = puc; T_UC * ucepos = puc + nuclen; while(utf8bpos<utf8epos && ucbpos< ucepos) { if (*utf8bpos < 0x80) //asc { *ucbpos++ = *utf8bpos++; } else if ( (*utf8bpos&0xE0) == 0xE0 ) //三個(gè)字節(jié) { if (ucepos - ucbpos < 2) { break; } *ucbpos = (T_UC(*utf8bpos++ & 0x0F)) << 12; *ucbpos |= (T_UC(*utf8bpos++ & 0x3F)) << 6; *ucbpos++ |= (T_UC(*utf8bpos++ & 0x3F)); } else if ((*utf8bpos&0xC0) == 0xC0) { if (ucepos - ucbpos < 2) { break; } *ucbpos = (T_UC(*utf8bpos++ & 0x1F)) << 6; *ucbpos++ |= (T_UC(*utf8bpos++ & 0x3F)); } else { utf8bpos++; } } return ucbpos-puc;}
那么Unicode和GBK編碼之間如何轉(zhuǎn)換呢?因?yàn)閁nicode和GBK之間沒有算法上面的對應(yīng)關(guān)系,只能通過查表來轉(zhuǎn)換。在Linux下面有iconv族函數(shù),可以輔助完成這一操作。以下是c++的實(shí)現(xiàn)代碼。
template <class _CS1, class _CS2>static int csconv(iconv_t tID, const _CS1* pcs1, size_t nlen1, _CS2* pcs2, size_t nlen2){ size_t nleft1 = nlen1*sizeof(_CS1); size_t nleft2 = nlen2*sizeof(_CS2); char* cpcs1 = (char*)pcs1; char* cpcs2 = (char*)pcs2; size_t nConv = iconv(tID, &cpcs1, &nleft1, &cpcs2, &nleft2); if (nConv==(size_t)-1) { return -1; } return (nlen2-nleft2/sizeof(_CS2));}int uc2gb(const T_UC* puc, size_t nuclen, T_GB* pgb, size_t ngblen){ iconv_t tID = iconv_open('GBK', 'UCS-2'); int len = csconv(m_tID, puc, nuclen, pgb, ngblen); iconv_close(tID); return len;}int gb2uc(const T_GB* pgb, size_t ngblen, T_UC* puc, size_t nuclen){ iconv_t tID = iconv_open('UCS-2', 'GBK'); int len = csconv(m_tID, pgb, ngblen, puc, nuclen) iconv_close(tID); return len;}因?yàn)閁nicode與GBK表示的字符集不一樣大,所以有很多Unicode字符沒有辦法轉(zhuǎn)化成GBK編碼。反過來就好多了,絕大多數(shù)GBK字符都可以正常轉(zhuǎn)換為Unicode字符。這里沒有說全部的GBK字符,是因?yàn)橛钟刑厥馇闆r,不過這個(gè)情況不用太關(guān)注,可以簡單的認(rèn)為所有的GBK字符都能正常轉(zhuǎn)換。
至于其他平臺(tái)的轉(zhuǎn)換方法,php有類似的iconv函數(shù),Java中就更簡單了:
String gbk = new String(unicode.getBytes('GBK'));
當(dāng)然,我們還可以用最原始的方法,就是自己實(shí)現(xiàn)查表的功能。不過查表法費(fèi)力不討好,建議不用。
有了Unicode和UTF-8的轉(zhuǎn)換,加上Unicode與GBK之間的轉(zhuǎn)換,那么UTF-8和GBK之間的轉(zhuǎn)換只需要用Unicode做一層中轉(zhuǎn)就好了。