中文字符編碼的相互轉換(三)

終於到討論編碼轉換這一步了。

先來看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字符都能正常轉換。

至於其他平臺的轉換方法,php有類似的iconv函數,Java中就更簡單了:
String gbk = new String(unicode.getBytes("GBK")); 

當然,我們還可以用最原始的方法,就是自己實現查表的功能。不過查表法費力不討好,建議不用。


有了Unicode和UTF-8的轉換,加上Unicode與GBK之間的轉換,那麼UTF-8和GBK之間的轉換只需要用Unicode做一層中轉就好了。

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