Unicode与UTF-8互相转换(C语⾔实现)
1. 基础
1.1 ASCII码
我们知道, 在计算机内部, 所有的信息最终都表⽰为⼀个⼆进制的字符串. 每⼀个⼆进制位(bit)有0和1两种状态, 因此⼋个⼆进制位就可以组合出 256种状态, 这被称为⼀个字节(byte). 也就是说, ⼀个字节⼀共可以⽤来表⽰256种不同的状态, 每⼀个状态对应⼀个符号, 就是256个符号, 从 0000000到11111111. 上个世纪60年代, 美国制定了⼀套字符编码, 对英语字符与⼆进制位之间的关系, 做了统⼀规定. 这被称为ASCII 码, ⼀直沿⽤⾄今.
ASCII码⼀共规定了128个字符的编码, ⽐如空格"SPACE"是32(⼆进制00100000), ⼤写的字母A是65(⼆进制01000001). 这128个符号(包括32个不能打印出来的控制符号), 只占⽤了⼀个字节的后⾯7位, 最前⾯的1位统⼀规定为0.
1.2 ⾮ASCII编码
英语⽤128个符号编码就够了, 但是⽤来表⽰其他语⾔, 128个符号是不够的. ⽐如, 在法语中, 字母上⽅有注⾳符号, 它就⽆法⽤ASCII码表⽰. 于是, ⼀些欧洲国家就决定, 利⽤字节中闲置的最⾼位编⼊新的符号.
⽐如, 法语中的é的编码为130(⼆进制10000010). 这样⼀来, 这些欧洲国家使⽤的编码体系, 可以表⽰最多256个符号. 但是, 这⾥⼜出现了新的问题. 不同的国家有不同的字母, 因此, 哪怕它们都使⽤256个符号的编码⽅式, 代表的字母却不⼀样. ⽐如, 130在法语编码中代表了é, 在希伯来语编码中却代表了字母Gimel (ג), 在俄语编码中⼜会代表另⼀个符号.
NOTE:
但是不管怎样, 所有这些编码⽅式中, 0-127表⽰的符号是⼀样的, 不⼀样的只是128-255 的这⼀段. // MMMMM
⾄于亚洲国家的⽂字, 使⽤的符号就更多了, 汉字就多达10万左右. ⼀个字节只能表⽰ 256种符号, 肯定是不够的, 就必须使⽤多个字节表达⼀个符号. ⽐如, 简体中⽂常见的编码⽅式是GB2312, 使⽤两个字节表⽰⼀个汉字, 所以理论上最多可以表⽰ 256x256=65536个符号.
2. Unicode
2.1 Unicode的定义
正如上⼀节所说, 世界上存在着多种编码⽅式, 同⼀个⼆进制数字可以被解释成不同的符号. 因此, 要想打开⼀个⽂本⽂件, 就必须知道它的编码⽅式, 否则⽤错误的编码⽅式解读, 就会出现乱码. 为什么电⼦
邮件常常出现乱码?就是因为发信⼈和收信⼈使⽤的编码⽅式不⼀样.
可以想象, 如果有⼀种编码, 将世界上所有的符号都纳⼊其中. 每⼀个符号都给予⼀个独⼀⽆⼆的编码, 那么乱码问题就会消失. 这就是Unicode, 就像它的名字都表⽰的, 这是⼀种所有符号的编码.
Unicode也是⼀种字符编码⽅法, 不过它是由国际组织设计, 可以容纳全世界所有语⾔⽂字的编码⽅案. Unicode的学名是"Universal Multiple-Octet Coded Character Set", 简称为UCS. UCS可以看作是"Unicode Character Set"的缩写.
Unicode当然是⼀个很⼤的集合, 现在的规模可以容纳100多万个符号. 每个符号的编码都不⼀样, ⽐如, U+0639表⽰阿拉伯字母Ain,
U+0041表⽰英语的⼤写字母A, U+4E25表⽰汉字"严". 具体的符号对应表, 可以查询, 或者专门的汉字对应表.
2.2 Unicode的问题
需要注意的是, "Unicode只是⼀个符号集, 它只规定了符号的⼆进制代码, 却没有规定这个⼆进制代码应该如何存储".
⽐如, 汉字"严"的unicode是⼗六进制数4E25, 转换成⼆进制数⾜⾜有15位 (100111000100101), 也就是说这个符号的表⽰⾄少需要2个字节.表⽰其他更⼤的符号, 可能需要3个字节或者4个字节, 甚⾄更多.
这⾥就有两个严重的问题, 第⼀个问题是, 如何才能区别unicode和ascii?计算机怎么知道三个字节表⽰⼀个符号, ⽽不是分别表⽰三个符号呢?第⼆个问题是, 我们已经知道, 英⽂字母只⽤⼀个字节表⽰就够了, 如果unicode统⼀规定, 每个符号⽤三个或四个字节表⽰, 那么每个英⽂字母前都必然有⼆到三个字节是0, 这对于存储来说是极⼤的浪费, ⽂本⽂件的⼤⼩会因此⼤出⼆三倍, 这是⽆法接受的. 它们造成的结果是:
1) 出现了unicode的多种存储⽅式, 也就是说有许多种不同的⼆进制格式, 可以⽤来表⽰unicode.
2) unicode在很长⼀段时间内⽆法推⼴, 直到互联⽹的出现
3. UTF-8
互联⽹的普及, 强烈要求出现⼀种统⼀的编码⽅式. UTF-8就是在互联⽹上使⽤最⼴的⼀种unicode的实现⽅式. 其他实现⽅式还包括UTF-16和UTF-32, 不过在互联⽹上基本不⽤. 重复⼀遍, 这⾥的关系是, UTF-8是Unicode的实现⽅式之⼀.
UTF-8最⼤的⼀个特点, 就是它是⼀种变长的编码⽅式. 它可以使⽤1~6个字节表⽰⼀个符号, 根据不同
的符号⽽变化字节长度.
3.1 UTF-8的编码规则
UTF-8的编码规则很简单, 只有两条:
1) 对于单字节的符号, 字节的第⼀位设为0, 后⾯7位为这个符号的unicode码. 因此对于英语字母, UTF-8编码和ASCII码是相同的.
2) 对于n字节的符号(n>1), 第⼀个字节的前n位都设为1, 第n+1位设为0, 后⾯字节的前两位⼀律设为10. 剩下的没有提及的⼆进制位, 全部为这个符号的unicode码.
下表总结了编码规则, 字母x表⽰可⽤编码的位.
| Unicode符号范围 | UTF-8编码⽅式
n | (⼗六进制) | (⼆进制)
---+-----------------------+------------------------------------------------------
1 | 00000000 - 0000 007F | 0xxxxxxx
2 | 00000080 - 0000 07FF | 110xxxxx 10xxxxxx
3 | 00000800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
4 | 00010000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5 | 00200000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 | 04000000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
表1. UTF-8的编码规则
下⾯, 还是以汉字"严"为例, 演⽰如何实现UTF-8编码.
已知"严"的unicode是4E25(1001110 00100101), 根据上表, 可以发现4E25处在第三⾏的范围内(0000 0800 - 0000 FFFF), 因此"严"的UTF-8编码需要三个字节, 即格式是 "1110xxxx 10xxxxxx 10xxxxxx". 然后, 从"严"的最后⼀个⼆进制位开始, 依次从后向前填⼊格式中的x, 多出的位补0. 这样就得到了, "严"的UTF-8编码是 "111001001011100010100101", 转换成⼗六进制就是E4B8A5.
4. Little endian和Big endian
上⼀节已经提到, Unicode码可以采⽤UCS-2格式直接存储. 以汉字"严"为例, Unicode码是4E25, 需要⽤两个字节存储, ⼀个字节是4E, 另⼀个字节是25. 存储的时候, 4E在前, 25在后, 就是Big endian⽅式; 25在前, 4E在后, 就是Little endian⽅式.
// Big Endian(4E25) Little Endian(254E)
因此, 第⼀个字节在前, 就是"⼤头⽅式"(Big endian), 第⼆个字节在前就是"⼩头⽅式 "(Little endian).
4.1 计算机怎么知道某⼀个⽂件到底采⽤哪⼀种⽅式编码?(零宽度⾮换⾏空格(FEFF))
Unicode规范中定义, 每⼀个⽂件的最前⾯分别加⼊⼀个表⽰编码顺序的字符, 这个字符的名字叫做"零宽度⾮换⾏空格"(ZERO WIDTH NO-BREAK SPACE), ⽤FEFF表⽰. 这正好是两个字节, ⽽且FF⽐FE⼤1.
// Big Endian(FEFF) Little Endian(FFFE)
NOTE:
如果⼀个⽂本⽂件的头两个字节是FE FF, 就表⽰该⽂件采⽤⼤头⽅式; 如果头两个字节是FF FE, 就表⽰该⽂件采⽤⼩头⽅式.
5. Unicode与UTF-8之间的转换
从表1我们很明显可以得知Unicode与UTF-8的关系, 下⾯以C语⾔实现两者之间的转换.
1) 将⼀个字符的Unicode(UCS-2和UCS-4)编码转换成UTF-8编码.
// #c---
/*****************************************************************************
* 将⼀个字符的Unicode(UCS-2和UCS-4)编码转换成UTF-8编码.
*
* 参数:
* unic 字符的Unicode编码值
* pOutput 指向输出的⽤于存储UTF8编码值的缓冲区的指针
* outsize pOutput缓冲的⼤⼩
*
* 返回值:
* 返回转换后的字符的UTF8编码所占的字节数, 如果出错则返回 0 .
*
* 注意:
* 1. UTF8没有字节序问题, 但是Unicode有字节序要求;
* 字节序分为⼤端(Big Endian)和⼩端(Little Endian)两种;
* 在Intel处理器中采⽤⼩端法表⽰, 在此采⽤⼩端法表⽰. (低地址存低位)
* 2. 请保证 pOutput 缓冲区有最少有 6 字节的空间⼤⼩!
****************************************************************************/
int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput,
int outSize)
{
assert(pOutput != NULL);
assert(outSize >= 6);
if ( unic <= 0x0000007F )
{
// * U-00000000 - U-0000007F: 0xxxxxxx
*pOutput = (unic & 0x7F);
return1;
}
else if ( unic >= 0x00000080 && unic <= 0x000007FF )
{
// * U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
*(pOutput+1) = (unic & 0x3F) | 0x80;
*pOutput = ((unic >> 6) & 0x1F) | 0xC0;
return2;
}
else if ( unic >= 0x00000800 && unic <= 0x0000FFFF )
{
// * U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
*(pOutput+2) = (unic & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 6) & 0x3F) | 0x80;
*pOutput = ((unic >> 12) & 0x0F) | 0xE0;
return3;
}
else if ( unic >= 0x00010000 && unic <= 0x001FFFFF )
{
// * U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*(pOutput+3) = (unic & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 12) & 0x3F) | 0x80;
*pOutput = ((unic >> 18) & 0x07) | 0xF0;
return4;
}
else if ( unic >= 0x00200000 && unic <= 0x03FFFFFF )
{
// * U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*(pOutput+4) = (unic & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 18) & 0x3F) | 0x80;
*pOutput = ((unic >> 24) & 0x03) | 0xF8;
return5;
}
else if ( unic >= 0x04000000 && unic <= 0x7FFFFFFF )
{
// * U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx *(pOutput+5) = (unic & 0x3F) | 0x80;
*(pOutput+4) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 18) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 24) & 0x3F) | 0x80;
*pOutput = ((unic >> 30) & 0x01) | 0xFC;
return6;
}
return0;
}
// #c---end
2) 将⼀个字符的UTF8编码转换成Unicode(UCS-2和UCS-4)编码.
<span xmlns="/1999/xhtml" >// #c---
/*****************************************************************************
* 将⼀个字符的UTF8编码转换成Unicode(UCS-2和UCS-4)编码.
*
* 参数:
* pInput 指向输⼊缓冲区, 以UTF-8编码
* Unic 指向输出缓冲区, 其保存的数据即是Unicode编码值,
* 类型为unsigned long .
*
* 返回值:
* 成功则返回该字符的UTF8编码所占⽤的字节数; 失败则返回0.
*
* 注意:
* 1. UTF8没有字节序问题, 但是Unicode有字节序要求;
* 字节序分为⼤端(Big Endian)和⼩端(Little Endian)两种;
* 在Intel处理器中采⽤⼩端法表⽰, 在此采⽤⼩端法表⽰. (低地址存低位)
****************************************************************************/
int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic)
{
assert(pInput != NULL && Unic != NULL);
// b1 表⽰UTF-8编码的pInput中的⾼字节, b2 表⽰次⾼字节, ...
char b1, b2, b3, b4, b5, b6;
*Unic = 0x0; // 把 *Unic 初始化为全零
int utfbytes = enc_get_utf8_size(*pInput);
unsigned char *pOutput = (unsigned char *) Unic;
switch ( utfbytes )
{
case0:
*pOutput = *pInput;
utfbytes += 1;
break;
case2:
b1 = *pInput;
b2 = *(pInput + 1);
if ( (b2 & 0xE0) != 0x80 )
return0;
*pOutput = (b1 << 6) + (b2 & 0x3F);
*(pOutput+1) = (b1 >> 2) & 0x07;
break;
case3:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) )
return0;
*pOutput = (b2 << 6) + (b3 & 0x3F);
*(pOutput+1) = (b1 << 4) + ((b2 >> 2) & 0x0F);
break;
case4:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) )
return0;
*pOutput = (b3 << 6) + (b4 & 0x3F);
*(pOutput+1) = (b2 << 4) + ((b3 >> 2) & 0x0F);
*(pOutput+2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03);
break;
case5:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) ) return0;
*pOutput = (b4 << 6) + (b5 & 0x3F);
unicode汉字*(pOutput+1) = (b3 << 4) + ((b4 >> 2) & 0x0F);
*(pOutput+2) = (b2 << 2) + ((b3 >> 4) & 0x03);
*(pOutput+3) = (b1 << 6);
break;
case6:
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
b6 = *(pInput + 5);
if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80)
|| ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) || ((b6 & 0xC0) != 0x80) )
return0;
*pOutput = (b5 << 6) + (b6 & 0x3F);
*(pOutput+1) = (b5 << 4) + ((b6 >> 2) & 0x0F);
*(pOutput+2) = (b3 << 2) + ((b4 >> 4) & 0x03);
*(pOutput+3) = ((b1 << 6) & 0x40) + (b2 & 0x3F);
break;
default:
return0;
break;
}
return utfbytes;
}
// #c---end
</span>
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论