1.UTF-8 转换 Unicode
在编写FTP Client时,发现通过recv获取的数据是采用UTF-8方式进行编码的,直接用Unicode方式进行显示时会发生错误。采用MultiByteToWideChar也无法正确转换(default是Ascii to Unicode。是我的设置问题?没有仔细研究)。
因此学习了下UTF-8的编码原理,参考如下:
标准的UTF-8是有一个头(是EF BB BF)和Unicode有一个(FF FE头一样),每一个字可以由一个byte(如:英文字母、数字),也可以由二个byte(如:泛欧语系、斯拉夫语字母),也可以由三个byte组成(如:汉字)。一般由四个byte组成的字很少很少。UTF-8编码模板如下:
UCS-4 range (hex.) UTF-8 octet sequence (binary)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
从这个头之后的第一个byte,如果是0xxxxxxx形式,就说明这个字只有一个byte组成,如果是110xxxxx形式就说明这个字有二个byte组成,如果是1110xxxx形式就说明有三个byte组成,如果是11110xxx就说明有四个byte组成。
转换算法可以简单的理解为:
首先判断这个UTF-8有没有头(EF BB BF),没有从第一个byte开始处理,有头就跳三个byte,读到真正的内容。没有的话,第一个byte就是真正的内容。再把读到的第一个byte分析成(0000 0000---1111 1111)和UTF-8编码格式相比较,得出这个真正的字有几个位。如果是只有一个byte(格式为0xxxxxxx),直接就把这个字符以宽字符写进文件就行了。如果是二byte(格式为110xxxxx),我们就要算出xxxxx处的值,再读一个byte得到10xxxxxx也同样要算出xxxxxx,把这两个连在一起xxxxx和xxxxx组成一个二进制数,得到这个二进数的十进制值,再以宽字符写进文件就行了。如果是有三位(格式为1110xxxx),要再读两个byte得到10xxxxxx和10xxxxxx,把这xxxx 和xxxxxx和xxxxxx连在一起组成二进制数,把他再转成十进制,把这个值以宽字符写进文件。三个字节、四个字节的一次类推。
简单的转换code参考Unicode下char*(UTF-8)转CString(Unicode):
CString CTTXDlg::g_f_pchar_to_wcs(const unsigned char * p )
{
CString wstr(_T(""));
wchar_t wc=L'1';
size_t r=0; //size_t unsigned integer Result of sizeof operator
while (1)
{
if(*p==NULL||*p==char(0))//原文为*p=='0',有错,改*p==char(0)
break;
r=g_f_u8towc(wc,p);//从UTF-8格式的地址中,读一个wchar_t出来
if(r==-1)//出现错误
AfxMessageBox(_T("g_f_u8towc error"));
p=p+r; //移位,准备下一次的转换
wstr+=wc;//给CString 附值
}
return wstr;
}
size_t CTTXDlg::g_f_u8towc(wchar_t &dest_wchar, const unsigned char * src_str)
{
int count_bytes = 0;
unsigned char byte_one = 0, byte_other = 0x3f; // 用于位与运算以提取位值 0x3f-->00111111
wchar_t tmp_wchar = L'0';
if (!src_str)
return (size_t)-1;
for (;;) // 检测字节序列长度,根据第一个字节头的1个个数
{
if (src_str[0] <= 0x7f){
count_bytes = 1; // ASCII字符: 0xxxxxxx( ~ 01111111)
byte_one = 0x7f; // 用于位与运算, 提取有效位值, 下同 01111111
break;
}
if ( (src_str[0] >= 0xc0) && (src_str[0] <= 0xdf) ){
count_bytes = 2; // 110xxxxx(110 00000 ~ 110 111111)
byte_one = 0x1f; //00011111 第一字节有效位的个数
break;
}
if ( (src_str[0] >= 0xe0) && (src_str[0] <= 0xef) ){
count_bytes = 3; // 1110xxxx(1110 0000 ~ 1110 1111)
byte_one = 0xf; //00001111
break;
}
if ( (src_str[0] >= 0xf0) && (src_str[0] <= 0xf7) ){
count_bytes = 4; // 11110xxx(11110 000 ~ 11110 111)
byte_one = 0x7;
break;
}
if ( (src_str[0] >= 0xf8) && (src_str[0] <= 0xfb) ){
count_bytes = 5; // 111110xx(111110 00 ~ 111110 11)
byte_one = 0x3;
break;
}
if ( (src_str[0] >= 0xfc) && (src_str[0] <= 0xfd) ){
count_bytes = 6; // 1111110x(1111110 0 ~ 1111110 1)
byte_one = 0x1;
break;
}
return (size_t)-1; // 以上皆不满足则为非法序列
}
// 以下几行析取UTF-8编码字符各个字节的有效位值
//先得到第一个字节的有效位数据
tmp_wchar = src_str[0] & byte_one;
for (int i=1; i<count_bytes;i++)//原文这里不完整,补充了
{
tmp_wchar <<= 6; // 左移6位后与后续字节的有效位值"位或"赋值
tmp_wchar = tmp_wchar | (src_str[i] & byte_other);//先与后或
}
// 位值析取__End!
dest_wchar = tmp_wchar;
return count_bytes;
}
友情感谢:
http://www.magicwolf.cn/hobby/utf-8-to-unicode.html
http://blog.sina.com.cn/s/blog_4d25c9870100chmu.html
http://blog.sina.com.cn/s/blog_697414470100qkgm.html
如果把各种文字编码形容为各地的方言,那么Unicode就是世界各国合作开发的一种语言。
在这种语言环境下,不会再有语言的编码冲突,在同屏下,可以显示任何语言的内容,这就是Unicode的最大好处。那么Unicode是如何编码的呢?其实非常简单。
就是将世界上所有的文字用2个字节统一进行编码。可能你会问,2个字节最多能够表示65536个编码,够用吗?
韩国和日本的大部分汉字都是从中国传播过去的,字型是完全一样的。
比如:“文”字,GBK和SJIS中都是同一个汉字,只是编码不同而已。
那样,像这样统一编码,2个字节就已经足够容纳世界上所有的语言的大部分文字了。
Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。
unicode包含两个概念,一是字符集,二是编码方式。utf-8是unicode的其中一种编码方式。
UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII 字节前插入三个 0x00.
在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '\0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.
UTF-8 有一下特性:
Unicode与UTF-8之间的转换
“严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。
在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击“文件”菜单中的“另存为”命令,会跳出一个对话框,在最底部有一个“编码”的下拉条。
1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。
3)Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。
4)UTF-8编码,也就是上一节谈到的编码方法。
需要在哪些时候注意编码问题?
1. 从外部资源读取数据:
InputStream ins = new FileInputStream("Test.java");
InputStreamReader instrd= new InputStreamReader(in, "GB2312");
//我们采用了GB2312编码读取外部数据,通过查看streamReader的encoding可以印证:
assertEquals("GB2312", instrd.getEncoding());
2. 字符串和字节数组的相互转换:
我们通常通过以下代码把字符串转换成字节数组:
"string".getBytes(); 等价于 "string".getBytes(Charset.defaultCharset());
如何从字节数组创建一个字符串呢:
new String("string".getBytes());