GB2312、Unicode编码等
- 抛出问题:
我在CPP文件中,打算输出一行阿拉伯字符:
当试图运行时,会弹出以下提示:
即便点击“是”,运行之后也是显示不出来的:
为什么会出现这种情况?我们先拿txt文件做个解释:
如果你在txt文件中输入阿拉伯文
另存为ANSI格式后,再打开是这样的:
但如果你另存为utf-8编码格式,就能正确显示:
VS中的cpp文件、.h文件也是同样的道理,这里显然是保存格式的问题,我们回到VS。
通过高级保存选项:
可以看到cpp文件的编码格式:
或者
这样的编码格式设置。
- GB2312:
介绍下GB2312是信息交换汉字编码字符集,适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆。
如果强制使用ASNI编码输出则会输出乱码:
如果按照Shift—JIS编码,会输出:
我们改为一段日文进行尝试:
输出结果就是正确的:
当我们把编码格式转变为朝鲜语时:
日文就显示不出来,但是其16进制编码还是可以显示出来的:
我们发现尽管日文没有显示出来,但是其编码是一样的。
其实不论编码格式是怎样的,字符在内存当中的存在都是以固定的二进制形式存在的,只是你用不同的编码方式去解析它,它被不同的解析方式解析之后,显示出来的字符就不同。比如你用GB2312编码格式去解析日文编码格式,就能成功解析日文,原因是GB2312编码中涵盖了这些日文。再比如你用阿拉伯语言编码格式去解析这三个日文字符就解析成这样:
原因就在于虽然在内存中的存在是aaef、aabf、aab7这样的十六进制形式,但有可能你在使用的这个阿拉伯文编码格式中的阿拉伯字母编码都在3f以下,所以你只能解析3f以下的,这里的aaef、aabf、aab7显然超过了3f就被阿拉伯编码按照最大的3f进行解析了。
- Unicode编码:
下面再来说Unicode编码。从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。前边的GB2312可以显示中文、日文等字符,但是对于阿拉伯文等字符就无能为力了,这里就要依靠Unicode编码。Unicode又叫做统一码或者万国码是一种在计算机上使用的字符编码,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。在Unicode中:例如,汉字“字”对应的数字是23383,我们有很多方式将数字23383表示成程序中的数据,包 括:UTF-8、UTF-16、UTF-32。
例如,“汉字”对应的数字是 0x6c49和0x5b57,而编码的程序数据是:
BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码
这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF- 32分别以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4 个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF- 16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。
这里说下Unicode编码和UTF-8的关系,Unicode是一个字符集,而UTF-8是Unicode的其中一种,Unicode是定长的都为双字节,而UTF-8是可变的,对于汉字来说Unicode占有的字节比UTF-8占用的字节少1个字节。Unicode为双字节,而UTF-8中汉字占三个字节。
Unicode编码和UTF-8编码的对应关系如下:
- UTF-8:
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0×00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。 UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。总结了一下规律:UTF-8的第一个字节开始的1的个数代表了总的编码字节数,后续字节都是以10开始。由上面的规则可以清晰的看出UTF-8编码克服了中文编码的两个问题。
例1:“汉”字的Unicode编码是0x6C49。0x6C49在0×0800-0xFFFF之间,使用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
例2:Unicode编码0x20C30在0×010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
- UTF-16
UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:如果U<0×10000,U的 UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。中文范围 4E00-9FBF,所以在UTF-16编码里中文2个字节编码。如果U≥0×10000,我们先计算U’=U-0×10000,然后将U’写成二进制形 式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
- UTF-32
UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。
- 字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。 例如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是 “乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM是Byte order Mark。在
UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以
不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。UTF-
8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是
EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
参考:
http://www.dreamdu.com/blog/2008/08/02/character_encoding/
这篇文章写得很详细:
http://www.cnblogs.com/cy163/archive/2007/05/31/766886.html
这篇《中文编码杂谈》写得不错,里边还有UTF-8判断以及编码转换的C++实现,推荐大家看下:
http://www.cnblogs.com/xkfz007/articles/2566434.html
这篇文章中也有判断是否是UTF-8编码的源码:
http://m.blog.csdn.net/blog/zison_sun/5815746
但是当你的文本如果原本就只有英文字符,那么即使你的文本是GB2312,也会被判别为是UTF-8.因为GB2312中的英文字符和UTF-8中英文字符原本就一样,如果你的文中全是英文字符,这个代码会认为所有的字符都是UTF-8中属于范围00000000-0000007F中的字符。