Unicode字符集和多字节字符集关系
Unicode字符集和多字节字符集关系
在计算机中字符通常并不是保存为图像,每个字符都是使⽤⼀个编码来表⽰的,⽽每个字符究竟使⽤哪个编码代表,要取决于使⽤哪个字符集(charset)。
在最初的时候,Internet上只有⼀种字符集——ANSI的ASCII字符集,它使⽤7 bits来表⽰⼀个字符,总共表⽰128个字符,其中包括了英⽂字母、数字、标点符号等常⽤字符。之后,⼜进⾏扩展,使⽤8 bits表⽰⼀个字符,可以表⽰256个字符,主要在原来的7 bits字符集的基础上加⼊了⼀些特殊符号例如制表符。
后来,由于各国语⾔的加⼊,ASCII已经不能满⾜信息交流的需要,因此,为了能够表⽰其它国家的⽂字,各国在ASCII的基础上制定了⾃⼰的字符集,这些从ANSI标准派⽣的字符集被习惯的统称为ANSI字符集,它们正式的名称应该是MBCS(Multi-Byte Chactacter System,即多字节字符系统)。这些派⽣字符集的特点是以ASCII 127 bits为基础,兼容ASCII 127,他们使⽤⼤于128的编码作为⼀个Leading Byte,紧跟在Leading Byte后的第⼆(甚⾄第三)个字符与Leading Byte⼀起作为实际的编码。这样的字符集有很多,我们常见的GB-2312就是其中之⼀。
例如在GB-2312字符集中,“连通”的编码为C1 AC CD A8,其中C1和CD就是Leading Byte。前127个编码为标准ASCII保留,例
如“0”的编码是30H(30H表⽰⼗六进制的30)。软件在读取时,如果看到30H,知道它⼩于128就是标准ASCII,表⽰“0”,看到
C1⼤于128就知道它后⾯有⼀个另外的编码,因此C1 AC⼀同构成⼀个整个的编码,在GB-2312字符集中表⽰“连”。
由于每种语⾔都制定了⾃⼰的字符集,导致最后存在的各种字符集实在太多,在国际交流中要经常转换字符集⾮常不便。因此,提出了Unicode字符集,它固定使⽤16 bits(两个字节、⼀个字)来表⽰⼀个字符,共可以表⽰65536个字符。将世界上⼏乎所有语⾔的常⽤字符收录其中,⽅便了信息交流。标准的Unicode称为UTF-16。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8,使⽤类似MBCS的⽅式对Unicode进⾏编码。注意UTF-8是编码,它属于Unicode字符集。Unicode字符集有多种编码形式,⽽ASCII只有⼀种,⼤多数MBCS(包括GB-2312)也只有⼀种。Unicode的最初⽬标,是⽤1个16位的编码来为超过65000字符提供映射。但这还不够,它不能覆盖全部历史上的⽂字,也不能解决传输的问题 (implantation head-ache's),尤其在那些基于⽹络的应⽤中。已有的软件必须做⼤量的⼯作来程序16位的数据。因此,Unicode⽤⼀些基本的保留字符制定了三套编码⽅式。它们分别是UTF-
8,UTF-16和UTF-32。正如名字所⽰,在UTF-8中,字符是以8位序列来编码的,⽤⼀个或⼏个字节来表⽰⼀个字符。这种⽅式的最⼤好处,是UTF-8保留了ASCII字符的编码做为它的⼀部分,例如,在UTF-8和ASCII中,“A”的编码都是0x41.UTF-16和UTF-32分别是Unicode的16位和32位编码⽅式。考虑到最初的⽬的,通常说的Unicode就是指UTF-16。
例如“连通”两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90
⽽其UTF-8编码为:E8 BF 9E E9 80 9A
最后,当⼀个软件打开⼀个⽂本时,它要做的第⼀件事是决定这个⽂本究竟是使⽤哪种字符集的哪种编码保存的。软件有三种途径来决定⽂本的字符集和编码:
最标准的途径是检测⽂本最开头的⼏个字节,如下表:
开头字节 Charset/encoding
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.例如插⼊标记后,连通”两个字的UTF-16 (big endian)和UTF-8码分别为:
FF FE DE 8F 1A 90
EF BB BF E8 BF 9E E9 80 9A
但是MBCS⽂本没有这些位于开头的字符集标记,更不幸的是,⼀些早期的和⼀些设计不良的软件在保存Unicode⽂本时不插⼊这些位于开头的字符集标记。因此,软件不能依赖于这种途径。这时,软件可以采取⼀种⽐较安全的⽅式来决定字符集及其编码,那就是弹出⼀个对话框来请⽰⽤户,例如将那个“连通”⽂件拖到MS Word中,Word就会弹出⼀个对话框。
如果软件不想⿇烦⽤户,或者它不⽅便向⽤户请⽰,那它只能采取⾃⼰“猜”的⽅法,软件可以根据整个⽂本的特征来猜测它可能属于哪个charset,这就很可能不准了。使⽤记事本打开那个“连通”⽂件就属于这种情况。
我们可以证明这⼀点:在记事本中键⼊“连通”后,选择“Save As”,会看到最后⼀个下拉框中显⽰有“ANSI”,这时保存。当再当打开“连通”⽂件出现乱码后,再点击“File”->“Save As”,会看到最后⼀个
下拉框中显⽰有“UTF-8”,这说明记事本认为当前打开的这个⽂本是⼀个UTF-8编码的⽂本。⽽我们刚才保存时是⽤ANSI字符集保存的。这说明,记事本猜测了“连通”⽂件的字符集,认为它更像⼀个UTF-8编码⽂本。这是因为“连通”两个字的GB-2312编码看起来更像UTF-8编码导致的,这是⼀个巧合,不是所有⽂字都这样。可以使⽤记事本的打开功能,在打开“连通”⽂件时在最后⼀个下拉框中选择ANSI,就能正常显⽰了。反过来,如果之前保存时保存为UTF-8编码,则直接打开也不会出现问题。
如果将“连通”⽂件放⼊MS Word中,Word也会认为它是⼀个UTF-8编码的⽂件,但它不能确定,因此会弹出⼀个对话框询问⽤户,这时选择“简体中⽂(GB2312)”,就能正常打开了。记事本在这⼀点上做得⽐较简化罢了,这与这个程序的定位是⼀致的。
需要提醒⼤家的是,部分Windows 2000字型⽆法显⽰所有的Unicode字符。如果发现⽂件中缺少了某些字符,只需将其变更为其它字型
需要提醒⼤家的是,部分Windows 2000字型⽆法显⽰所有的Unicode字符。如果发现⽂件中缺少了某些字符,只需将其变更为其它字型即可。
big endian和little endian
big endian和little endian是CPU处理多字节数的不同⽅式。例如“汉”字的Unicode编码是6C49。那么写
到⽂件⾥时,究竟是将6C写在前⾯,还是将49写在前⾯?如果将6C写在前⾯,就是big endian。还是将49写在前⾯,就是little endian。
“endian”这个词出⾃《格列佛游记》。⼩⼈国的内战就源于吃鸡蛋时是究竟从⼤头(Big-Endian)敲开还是从⼩头(Little-Endian)敲开,由此曾发⽣过六次叛乱,其中⼀个皇帝送了命,另⼀个丢了王位。
我们⼀般将endian翻译成“字节序”,将big endian和little endian称作“⼤尾”和“⼩尾”。
Unicode big endian:在Big-endian处理器(如苹果Macintosh电脑)上建⽴的Unicode⽂件中的⽂字位元组(存放单位)排列顺序,与在Intel处理器上建⽴的⽂件的⽂字位元组排列顺序相反。最重要的位元组拥有最低的地址,且会先储存⽂字中较⼤的⼀端。为使这类电脑的⽤户能够存取你的⽂件,可选择Unicode big-endian格式。
>>>>>>>>>>>>>####
ANSI字符,UNICODE,宽字符,窄字符,多字节字符集
Unicode :宽字节字符集
1. 如何取得⼀个既包含单字节字符⼜包含双字节字符的字符串的字符个数?
可以调⽤Microsoft Visual C++的运⾏期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。
调⽤strlen函数,⽆法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。
2. 如何对DBCS(双字节字符集)字符串进⾏操作?
函数 描述
PTSTR CharNext ( LPCTSTR ); 返回字符串中下⼀个字符的地址
PTSTR CharPrev ( LPCTSTR, LPCTSTR ); 返回字符串中上⼀个字符的地址
BOOL IsDBCSLeadByte( BYTE ); 如果该字节是DBCS字符的第⼀个字节,则返回⾮0值
3. 为什么要使⽤Unicode?
(1) 可以很容易地在不同语⾔之间进⾏数据交换。
(2) 使你能够分配⽀持所有语⾔的单个⼆进制.exe⽂件或DLL⽂件。
(3) 提⾼应⽤程序的运⾏效率。
Windows 2000是使⽤Unicode从头进⾏开发的,如果调⽤任何⼀个Windows函数并给它传递⼀个ANSI字符串,那么系统⾸先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会⾸先将Unicode字符串转换成ANSI 字符串,然后将结果返回给你的应⽤程序。进⾏这些字符串的转换需要占⽤系统的时间和内存。通过从头开始⽤Unicode来开发应⽤程序,就能够使你的应⽤程序更加有效地运⾏。
Windows CE 本⾝就是使⽤Unicode的⼀种操作系统,完全不⽀持ANSI Windows函数
Windows 98 只⽀持ANSI,只能为ANSI开发应⽤程序。
Microsoft公司将COM从16位Windows转换成Win32时,公司决定需要字符串的所有COM接⼝⽅法都只能接受Unicode字符串。
4. 如何编写Unicode源代码?
Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码⽂件,以便使⽤或者不使⽤Unicode来对它进⾏编译。只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源⽂件。
_UNICODE宏⽤于C运⾏期头⽂件,⽽UNICODE宏则⽤于Windows头⽂件。当编译源代码模块时,通常必须同时定义这两个宏。
5. Windows定义的Unicode数据类型有哪些?
数据类型 说明
WCHAR Unicode字符
PWSTR 指向Unicode字符串的指针
PCWSTR 指向⼀个恒定的Unicode字符串的指针
对应的ANSI数据类型为CHAR,LPSTR和LPCSTR。
ANSI/Unicode通⽤数据类型为TCHAR,PTSTR,LPCTSTR。
6. 如何对Unicode进⾏操作?
字符集 特性 实例
ANSI 操作函数以str开头 strcpy
Unicode 操作函数以wcs开头 wcscpy
MBCS 操作函数以_mbs开头 _mbscpy
MBCS 操作函数以_mbs开头 _mbscpy
ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运⾏期库)
ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。ANSI版本函数结尾以A表⽰;Unicode版本函数结尾以W表⽰。Windows会如下定义:
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
ascii文字是啥#endif // !UNICODE
7. 如何表⽰Unicode字符串常量?
字符集 实例
ANSI “string”
Unicode L“string”
ANSI/Unicode T(“string”)或_TEXT(“string”)if( szError[0] == _TEXT(‘J’) ){ }
8. 为什么应当尽量使⽤操作系统函数?
这将有助于稍稍提⾼应⽤程序的运⾏性能,因为操作系统字符串函数常常被⼤型应⽤程序⽐如操作系统的外壳进程所使⽤。由于这些函数使⽤得很多,因此,在应⽤程序运⾏时,它们可能已经被装⼊RAM。
如:StrCat,StrChr,StrCmp和StrCpy等。
9.如何编写符合ANSI和Unicode的应⽤程序?
(1) 将⽂本串视为字符数组,⽽不是chars数组或字节数组。
(2) 将通⽤数据类型(如TCHAR和PTSTR)⽤于⽂本字符和字符串。
(3) 将显式数据类型(如BYTE和PBYTE)⽤于字节、字节指针和数据缓存。
(4) 将TEXT宏⽤于原义字符和字符串。
(5) 执⾏全局性替换(例如⽤PTSTR替换PSTR)。
(6) 修改字符串运算问题。例如函数通常希望在字符中传递⼀个缓存的⼤⼩,⽽不是字节。这意味着不应该传递sizeof(szBuffer),⽽应该传递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配⼀个内存块,并且拥有该字符串中的字符数⽬,那么请记住要按字节来分配内存。这就是说,应该调⽤malloc(nCharacters *sizeof(TCHAR)),⽽不是调⽤malloc(nCharacters)。
10. 如何对字符串进⾏有选择的⽐较?
通过调⽤CompareString来实现。
标志 含义
NORM_IGNORECASE 忽略字母的⼤⼩写
NORM_IGNOREKANATYPE 不区分平假名与⽚假名字符
NORM_IGNORENONSPACE 忽略⽆间隔字符
NORM_IGNORESYMBOLS 忽略符号
NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同⼀个字符
SORT_STRINGSORT 将标点符号作为普通符号来处理
11. 如何判断⼀个⽂本⽂件是ANSI还是Unicode?
判断如果⽂本⽂件的开头两个字节是0xFF和0xFE,那么就是Unicode,否则是ANSI。
12. 如何判断⼀段字符串是ANSI还是Unicode?
⽤IsTextUnicode进⾏判断。IsTextUnicode使⽤⼀系列统计⽅法和定性⽅法,以便猜测缓存的内容。由于这不是⼀种确切的科学⽅法,因此 IsTextUnicode有可能返回不正确的结果。
13. 如何在Unicode与ANSI之间转换字符串?
Windows函数MultiByteToWideChar⽤于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。
________________________________________________________________
UCS,UNICODE和UTF-8
UCS,UNICODE和UTF-8
本⽂简单介绍UCS,UNICODE和UTF-8,并利⽤C语⾔实现了UTF-8与UCS2之间的互相转化。
1.什么是UCS和ISO10646?
国际标准ISO10646定义了通⽤字符集(Universal Character Set, UCS). UCS是所有其它字符集标准的⼀个超集,它保证也其它字符集双向兼容,即编码间相互转换不会丢失任何信息。UCS字符集U+0000到U+007F与US-ASCII是⼀致的。
2.什么是UNICODE
历史上, 有两个独⽴的, 创⽴单⼀字符集的尝试. ⼀个是国际标准化组织(ISO)的 ISO 10646 项⽬, 另⼀
个是由(⼀开始⼤多是美国的)多语⾔软件制造商组成的协会组织的 Unicode 项⽬. 幸运的是, 1991年前后, 两个项⽬的参与者都认识到, 世界不需要两个不同的单⼀字符集. 它们合并双⽅的⼯作成果, 并为创⽴⼀个单⼀编码表⽽协同⼯作. 两个项⽬仍都存在并独⽴地公布各⾃的标准, 但 Unicode 协会和 ISO/IEC
JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何未来的扩展.
3.什么是UTF-8(⼀种传送和存储格式)
UCS和UNICODE为每个字符分配了⼀个对应的整数,但并没有明确说明其实现机制.故存在多种编码⽅式,其中以两个字节和四个字节来存储⼀个字符的⽅法分别叫UCS-2, UCS-4,要将⼀个ASCII⽂件转换成⼀个UCS-2⽂件只要在每个字节前加⼀个字节0X00,转换成UCS-4只要在每个字节前加三个0X00。
⽽internet上⼤量的信息是以ASCII码存在的,如果都⽤两个字节来存储将浪费⼤量的资源,同时Unix和Linux下使⽤USC-2和USC-4会导致严重问题,于是出现了UTF-8(定义于ISO10646-1).
UTF-8
(UTF-8 stands for Unicode Transformation Format-8. It is an octet (8-bit) lossless encoding of Unicod
e characters.) UNICODE(UCS)和UTF-8的对应关系。
U-00000000 - U-0000007F: 0xxxxxxx (ASCII码 最重⽤)
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 (很少⽤)
在多字节串中,第⼀个字节的开头‘1’的数⽬就是整个串中字节的数⽬.
下⾯举UCS-2与UTF-8的对应关系,并利⽤C语⾔实现之间的互相转化。
-----------------------------------------------------------------------------------------------------------
| UCS2 | UTF-8 |
|----------------------------------------------------------------------------------------------------------
| | code | 1st Byte | 2nd byte | 3rd Byte |
|--------------------------------------------------------------------------------------------------------
| 000000000aaaaaaa | 0000 - 007F | 0aaaaaaa | | |
|--------------------------------------------------------------------------------------------------------
| 00000bbbbbaaaaaa | 0080 - 07FF | 110bbbbb | 10aaaaaa | |
|--------------------------------------------------------------------------------------------------------
| ccccbbbbbbaaaaaa | 0800 - FFFF | 1110cccc | 10bbbbbb | 10aaaaaa |
|--------------------------------------------------------------------------------------------------------
alphajay的疑问: 这⾥是a b c应该是从0 1中取的⼀个bit位吧
因为USC-2 每个字符⽤两个字节 16bit表⽰
在这⼉我只实现了单个字符的转换,串的转换也是⼀样的道理。
1,把⼀个UTF-8字符转换成⼀个UCS-2字符。
如果转换成功返回1,如果UTF-8字符是⼀个unrecognized字符,则返回0,且存⼀个blackbox(U+22e0)到ucs2_code_ptr中。
typedef unsigned short UINT16;
typedef unsigned char UINT8;
typedef unsigned char BOOL;
#define TRURE (BOOL)(1)
#define FALSE (BOOL)(0)
BOOL UTF8toUCS2Code(const UINT8* utf8_code, UINT16* ucs2_code){
UINT16 temp1, temp2;
BOOL is_unrecognized = FALSE ;
UINT16 * in = utf8_code;
UINT16 * in = utf8_code;
if(!utf8_code || !ucs2_code){
return is_unrecognized;
}
if(0x00 == (*in & 0x80)){
/* 1 byte UTF-8 Charater.*/
*ucs2_code= (UINT16)*in;
is_unrecognized = TRUE;
}
else if(0xc0 == (*in & 0xe0) &&
0x80 == (*(in + 1) & 0xc0)
){
/* 2 bytes UTF-8 Charater.*/
temp1 = (UINT16)(*in & 0x1f);
temp1 <<= 6;
temp1 |= (UINT16)(*(in + 1) & 0x3f);
*ucs2_code = temp1;
is_unrecognized = TRUE;
}
else if( 0xe0 == (*in & 0xf0) &&
0x80 == (*(in +1) & 0xc0) &&
0x80 == (*(in + 2) & 0xc0)
){
/* 3bytes UTF-8 Charater.*/
temp1 = (UINT16)(*in &0x0f);
temp1 <<= 12;
temp2 = (UINT16)(*(in+1) & 0x3F);
temp2 <<= 6;
temp1 = temp1 | temp2 | (UINT16)(*(in+2) & 0x3F);
*ucs2_code = temp1;
is_unrecognized = TRUE;
}
else{
/* unrecognize byte. */
*ucs2_code = 0x22e0;
is_unrecognized = FALSE;
}
return is_unrecognized;
}
2,把⼀个UCS-2字符转换成UTF-8字符。函数返回转换成UTF-8的长度(字节1 -- 3),如果⽬标指针为空,返回0。UINT8 UCS2toUTF8Code(UINT16 ucs2_code, UINT8* utf8_code){
int length = 0;
UINT8* out = utf8_code;
if(!utf8_code){
return length;
}
if(0x0080 > ucs2_code){
/* 1 byte UTF-8 Character.*/
*out = (UINT8)ucs2_code;
length++;
}
else if(0x0800 > ucs2_code){
/*2 bytes UTF-8 Character.*/
*out = ((UINT8)(ucs2_code >> 6)) | 0xc0;
*(out+1) = ((UINT8)(ucs2_code & 0x003F)) | 0x80;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。

发表评论