您的位置:首页 > 其它

跨平台utf8转unicode研究实现(3)

2013-08-05 18:53 281 查看
前端时间要自己实现utf8和unicode格式转换,打算封装一个类。也想过偷懒,直接网上找一个拿来用,但是后来发现问题多多。首先:接口不一样,其次:网上大部分方法还是不靠谱的,或者说:方法现在已经已经不适用了。
关于二者转换的原理网上很多,这里就不赘述了。主要说一下我遇到的问题:

我希望写成的接口是这个样子的:



class CStrConvertor
{
public:                                  
	static int Unicode2Utf8(LPSTR cBuf, int& iCBuf,LPCWSTR uBuf, int iUBuf);
	static int Utf82Unicode(LPWSTR pDst,  int nDstLen , LPSTR pSrc,int nSrcLen);
};


亦即:外边分配好存储转换结果的空间,而后在方法中对其进行赋值。



问题1:如何做参数有效性检查。检查集中在结果存储空间是否足够大小上。由于utf8是变长的,在转换结束之前永远不知道其具体占用多大空间,所以不好先期判断。可能也有人说了:Unicode2Utf8时我直接开3倍+1大小的空间,Utf82Unicode直接开2k+2的空间不就足够了?在转换函数中直接判断结果存储空间达没达到这个大小不就OK了?的确,这样没错。但是作为一个完善的类,这样是不严谨的!比如:Unicode2Utf8时可能提供的结果串只需要2k空间就足够了,外边也开了2倍空间,但是我们函数中上来就判断其不到3倍+1然后给报错了,这显然不行!所以:用最大空间来判断不好!

解决:1:网上也有人这么做,就是结果存储空间是在转换函数内部分配的,外部只是声明了一个指针而已。这样随着转换的进行,同时追加分配新空间! 这样的确解决了上边的问题,但是分配空间本身是相当消耗资源的,这函数的效率就低了!

2:我用的方法:在转换前也做一次遍历,看每个字符转换后占用几个byte,进行累加。依据最后的值判断参数的有效性!这样虽然给人感觉多做了一遍,但是较为严谨!





问题2:比如在Utf82Unicode时,我们需要看每个byte的前几位是否满足utf8的格式定义,这要用到:从一个字节中取出前/后若干位来。网上很多方法都是简单的直接做了个移位操作,比如:0xff<<5 。但是这样显然会遇到一个很基础的错误!

把问题抽象出来:0xff << 5 和 0xe0是一样的吗?嗯,有时候一样,有时候不一样!当直接使用<< 或>>进行位移操作时:你要明白它在cpu中是如何进行的。比如一个32位宽的cpu,当执行0xff << 5时:高位移除的5位并没有丢失掉!毕竟32位宽,所以移位完之后变为:0x1fe0 ,这也是为什么0xff << 5 和 0xe不一定相等的原因。二者相等不相等取决于cpu的位宽!
网上很多算法都没有注意到这点,我猜测那些算法都是8位宽时代实现的,后来放网上时也没去修改。但是这种不同只有在左移时才发生,右移时不会发生!这提示我们:在使用时尽可能少用移位操作符,本身考虑的东西太多而且代码阅读性也不好。



---------------------------------------------------------------------------------------------------------------------------------------------------

基础补充:

1:一个unicode在内存中占用2个byte。而utf是一种存储格式,其是变长的,最长可达6byte。但是基本上3个byte已经包括目前所能用到的全部字符了。 所以:Unicode2Utf8时最坏情况为:每个Unicode转完后都占用3个byte,所以最大要开3倍空间,再加上有一个/0结束符,所以最大需要开3k+1的空间! 而Utf82Unicode时:最坏情况下每个Utf8都转为一个unicode,亦即原本的每个byte先变为2个byte,再加上最后两个byte的/0
所以最大需要开2k+2的空间!

---------------------------------------------------------------------------------------------------------------------------------------------------

下边是我封装的类代码,有错误大家指正。

/**
  *     该函数用于将Unicode数组转为utf8格式!
  * 	@param out   转换结果存放区域指针
  * 	@param outLength   转换结果存放区域大小
  * 	@param in   源串存放区域指针
  * 	@param inLength   源串存放区域大小
  * 	@return QINT 转换结果在目的串中实际使用的长度,转换失败则返回-1
  */
int CStrConvertor::Unicode2Utf8( char* out, int& outLength,const wchar_t * in, int inLength )
{
	//------------------------------------------------
	//参数有效性判断
	if(out == NULL || in == NULL || inLength<0)
	{
		return -1;
	}
	int totalNum = 0;
	for(int i = 0; i < inLength; i++)//计算转换结果实际所需长度
	{
		wchar_t unicode = in[i];
		if (unicode >= 0x0000 && unicode <= 0x007f)
        {
            totalNum += 1;
        }
        else if (unicode >= 0x0080 && unicode <= 0x07ff)
        {
            totalNum += 2;
        }
        else if (unicode >= 0x0800 && unicode <= 0xffff)
        {
            totalNum += 3;
        }
	}
	if( outLength < totalNum )//参数有效性判断!
	{
		return -1;
	}
	//------------------------------------------------

    int outsize = 0;//用来计数输出结果的实际大小!
    char *tmp = out;
	int i = 0;
    for (i = 0; i < inLength; i++)
    {
		if(outsize>outLength) //空间不足对应处理!
		{
			return -1;
		}
        wchar_t unicode = in[i];
        
        if (unicode >= 0x0000 && unicode <= 0x007f)
        {
            *tmp = (char)unicode;
            tmp += 1;
            outsize += 1;
        }
        else if (unicode >= 0x0080 && unicode <= 0x07ff)
        {
            *tmp = 0xc0 | (unicode >> 6);
            tmp += 1;
            *tmp = 0x80 | (unicode & (0xff >> 2));
            tmp += 1;
            outsize += 2;
        }
        else if (unicode >= 0x0800 && unicode <= 0xffff)
        {
            *tmp = 0xe0 | (unicode >> 12);
            tmp += 1;
            *tmp = 0x80 | (unicode >> 6 & 0x00ff);
            tmp += 1;
            *tmp = 0x80 | (unicode & (0xff >> 2));
            tmp += 1;
            outsize += 3;
        }
    }
	return outsize;
}
-------------------------------------------------------
/**
  * 该函数用于将utf8数组转为Unicode格式!
  * 目前该函数返回值为:转换后unicode数据占用的wchar_t的个数(切记不是总char的个数) !
  * 	@param out   转换结果存放区域指针
  * 	@param outsize   转换结果存放区域大小
  * 	@param in   源串存放区域指针
  * 	@param insize   源串存放区域大小
  * 	@return QINT 转换结果在目的串中的长度,转换失败则返回-1
  */ 
QINT  CStrConvertor::Utf82Unicode(LPWSTR out,  QINT outsize , LPSTR in,QINT insize)
{
	//-------------------------------------------------------------------------------------------
	//参数有效性判断
	if(out == NULL || in == NULL || insize<0)
	{
		return -1;
	}

	int totalNum = 0;
	char *p = in;
	for(int i=0;i<insize;i++)
	{
		if (*p >= 0x00 && *p <= 0x7f)//说明最高位为'0',这意味着utf8编码只有1个字节!
		{
			p++;
			totalNum += 1;
		}
		else if ((*p & (0xe0))== 0xc0)//只保留最高三位,看最高三位是不是110,如果是则意味着utf8编码有2个字节!
		{
			p++;
			p++;
			totalNum += 1;
		}
		else if ((*p & (0xf0))== 0xe0)//只保留最高四位,看最高三位是不是1110,如果是则意味着utf8编码有3个字节!
		{
			p++;
			p++;
			p++;
			totalNum += 1;
		}
	}
	if( outsize < totalNum )//参数有效性判断!
	{
		return -1;
	}
	//------------------------------------------------
		int resultsize = 0;

		p = in;
		char* tmp = (char *)out;
		while(*p)
		{
			if (*p >= 0x00 && *p <= 0x7f)//说明最高位为'0',这意味着utf8编码只有1个字节!
			{
				*tmp = *p;
				tmp++;
				//*tmp = '/0';
				tmp++;
				resultsize += 1;
			}
			else if ((*p & 0xe0)== 0xc0)//只保留最高三位,看最高三位是不是110,如果是则意味着utf8编码有2个字节!
			{
				wchar_t t = 0;
				char t1 = 0;
				char t2 = 0;

				t1 = *p & (0x1f);//高位的后5位!(去除了头部的110这个标志位)
				p++;
				t2 = *p & (0x3f);//低位的后6位!(去除了头部的10这个标志位)

				*tmp = t2 | ((t1 & (0x03)) << 6);
				tmp++;
				*tmp = t1 >> 2;//留下其保留的三位
				tmp++;
				resultsize += 1;
			}
			else if ((*p & (0xf0))== 0xe0)//只保留最高四位,看最高三位是不是1110,如果是则意味着utf8编码有3个字节!
			{
				wchar_t t = 0;
				wchar_t t1 = 0;
				wchar_t t2 = 0;
				wchar_t t3 = 0;
				t1 = *p & (0x1f);
				p++;
				t2 = *p & (0x3f);
				p++;
				t3 = *p & (0x3f);

				*tmp = ((t2 & (0x03)) << 6) | t3;
				tmp++;
				*tmp = (t1 << 4) | (t2 >> 2);
				tmp++;
				resultsize += 1;
			}
			p++;
		}
		/*不考虑结束符,如果考虑则打开此段!
		*tmp = '/0';
		tmp++;
		*tmp = '/0';
		resultsize += 2;
		*/
		return resultsize;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: