终于到讨论编码转换这一步了。
先来看Unicode和UTF-8之间的转换,前面我们说过Unicode和UTF-8的字符是一一对应的。他们的对应规则如下:
Unicode和UTF-8之间的转换关系表
UCS-4编码 | UTF-8字节流 |
U+00000000 – U+0000007F | 0xxxxxxx |
U+00000080 – U+000007FF | 110xxxxx 10xxxxxx |
U+00000800 – U+0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+00010000 – U+001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+00200000 – U+03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+04000000 – U+7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
以上表格摘自维基百科,该表格记录了UCS-4 与UTF-8的对应关系。上面的x表示我们可以编码的位。这个表记录的内容太多,我们平常使用只需要前三行,也就是UCS-2的表示范围。这基本可以表示我们国际上通用的所有文字和特殊符号了。
再来解释一下UTF-8编码字节含义:
对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
有了这层对应关系,Unicode到utf-8的转化代码就不难实现了,以下是我用c实现的,经多年线上验证没有问题。
typedef char T_GB; typedef unsigned short T_UC; typedef unsigned char T_UTF8; /*! * \brief UCS-2编码文本转换为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,可能会出现部分字符被截断 * \return int 转换后的字符长度 */ 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编码文本转换为UCS-2编码文本 * \param[in] putf8: UTF-8字符串的地址 * \param[in] nutf8len: UTF-8字符串的长度 * \param[out] puc: 输出的UCS-2字符串的地址 * \param[in] nuclen: 最大可以允许的UCS-2字符串的长度,如果nuclen<nutf8len,可能会出现部分字符被截断 * \return int 转换后的字符长度 */ 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 ) //三个字节 { 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编码之间如何转换呢?因为Unicode和GBK之间没有算法上面的对应关系,只能通过查表来转换。在Linux下面有iconv族函数,可以辅助完成这一操作。以下是c++的实现代码。
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; }因为Unicode与GBK表示的字符集不一样大,所以有很多Unicode字符没有办法转化成GBK编码。反过来就好多了,绝大多数GBK字符都可以正常转换为Unicode字符。这里没有说全部的GBK字符,是因为又有特殊情况,不过这个情况不用太关注,可以简单的认为所有的GBK字符都能正常转换。
String gbk = new String(unicode.getBytes("GBK"));
当然,我们还可以用最原始的方法,就是自己实现查表的功能。不过查表法费力不讨好,建议不用。
有了Unicode和UTF-8的转换,加上Unicode与GBK之间的转换,那么UTF-8和GBK之间的转换只需要用Unicode做一层中转就好了。
原文:http://blog.csdn.net/yulongli/article/details/22984375