字符集、字符编码、XML中的中文编码
作为程序员的你是不是对于ASCII 、UNICODE、GB2321、UTF-7、UTF-8等等不时出现在你面前的这些有着奇怪意义的词感到很讨厌呢,是不是总觉得好象明白一点又好象不是很明白它们真正的意义。下面我就来讲讲这些东东。
一、 字符集
字符集是什么呢,计算机表示某种语言所需要的符号和文字。它有很多种规范,例如
ASCII、GB2312、BIG5、GB18030、UNICODE,它们就是一些符号的集合,这些符号的索引值与具体存储到计算中的值并不等同。
(A)ASCII:最初的一批程序员在表示某个字母时是随意的,造成很大的麻烦,于是美国的标准化组织出台了(American Standard Code for Information Interchange, ASCII),它指定了用7或8位二进制数来表示,用八位二进制可以表示256个字符,前128个字符也叫标准ASCII码,后另外的128个叫扩展ASCII码,在这个时代程序员很幸福,因为我们明确的知道'A'就是65,存储到计算中也是65,一切都没问题。
(B)代码页:可惜幸福时光很短暂的,很快人们发现这256个字符不太够用了,有很多国家使用了不太一样的字母,比如德语、法语,这些国家定义了自已的标准,他们用8位表示他们的语言所需字母。于是我们就有了问题,因为计算机存储的总是二进制数据,那么一个值为65的byte是代表英文中的'A'呢还是别的语言的字母呢。在过去那种节省每一个byte的思想指导下,没人会去考虑其它的方法,于是前辈们想到用代码页(code page)来解决这个问题。只要告知数据是使用的何种代码页,然后就用这个值到这个代码页对应的256个字符集中去查找对应的字母就好了。非常优秀的解决方案,如同一个真理。
(C)多字符语言:不过真理也是有范围的,计算机的发展一日千里,老问题的新版本又来了,我们如何用字符来表示汉字,要知道汉字实在是太多了,怪不得中国人比老外就是聪明,能记得住这么多方块字符。256个字符是无论如何不能包容所有的汉字的。日文和韩文也是同样的问题。现在我们不得不扩充我们的字符集了,1980年国家标准总局提出了GB2312的解决办法,它共收入汉字6763个和非汉字图形字符682个,它具体规则如下:
GB 2312中对所收汉字进行了“分区”处理,共94个区,每区含有94个汉字/符号。这种表示方式也称为区位码。
01-09区为特殊符号。
16-55区为一级汉字,按拼音排序。
56-87区为二级汉字,按部首/笔画排序。
10-15区及88-94区则未有编码。
所以我们能够用一个区值和一个位值来查到对应的符号。比如第一个汉字"啊",它的区位码就是1601。当然我们的台湾同胞们也发展了BIG5(又称大五码)的标准,韩国和日本也有他们自已的标准。GB2312由于汉字太少,很多人名和古文无法进行录入,如前总理朱镕基的镕字就没有办法。不过由于它的标准制定很早,因此它是一个最广泛的汉字字符集标准。当然有部分厂商还共同发展了GBK标准,以及国家最新标准GB18030,感兴趣的同志可以百度一下。还有一种HZ码,它常见于internet。
(D)UNICODE:所谓天下大势分久必合,合久必分。虽然我们用“代码页”+“字符集”可以解决信息交换的问题,不过还是过于麻烦,因为我们还是要不断的在各种代码页中进行切换。好了,即然八个二进制位字符无法表示所有符号那么16个二进制位呢,32个二进制位呢,于是有了最初的UNICODE标准,采用2个byte,也就是16个位来表示,那么它可以表示2 16 = 65536 个字符,它包括了各种语言的基本字符集的各种符号,比如中文它使用了GB2312中的汉字符号,当然它没有被完全填充,不过也基本够用了,它与ISO 10646(通用字符集标准Universal Character Set,UCS)相对应,不过我们一般都只是知道UNICODE,这种采用2个byte的标准我们也叫UCS-2,然后有些砖家发现还有很多少数民族语言,生僻字无法容纳,又提出所谓的UCS-4,也就是用四个字节表示一个字符,虽然它实际只使用了31位,不过其空间也太大了,估计银河以外的种族所用文字以及古埃及祭祀用的文字都可以包括。这些离我们程序员有一点点遥远,你还需要更详细的可以再去百度一下。另外由于UNICODE字符集将各种语言汇集在一起,因此它只有开始的128个字符与标准ASCII的索引值兼容,而与其它的各类字符集其索引完全不同了。如GB2312中“汉”字为BABA,而UNICODE中为6C49
好了,字符集就介绍到这里,小结一下:字符集就象一个索引表,我们可以通过“代码页”+“索引值”来取得字符,当然对于UNICODE则不需要。
二、字符编码
字符编码就是指如何将数据存储到计算中,
(A)ASCII:很简单用一个byte就搞定,对于非英语国家就需要设定代码页。
(B)GB2312:因为这是一个使用很多的中文标准,特别说明一下。其实标准机构制定字符集标准时,同样也设定了存储标准。对于一个中文字不过我们无论如何也不可能用八个二进制位来表示了,它至少需要两个byte来表示,因为计算机中最小的存储单位是一个byte。当然为了兼容标准ASCII字符集,我们进行了一些小小的调整。回到前面,我们知道表示一个标准ASCII只需要七位就足够了,因此最高位总是0,而扩展的ASCII字符与中文没有什么太大关系,因此我们把表示一个中文字符的每个区值和位值分别加上A0,我们前面说了汉字“啊”的区位码是1601,于是它的内码表示(也就是计算存储方式)为B0A1,为什么要这样子处理,就是为了兼容标准ASCII的处理,因为汉字的两个byte值都大于A0,也就是高位为1,而标准ASCII字符都为0。
(C)UNICODE:将UNICODE字符进行编码存储最理想的是直接将其变成两个8位的值,好是好,不过它不兼容ASCII编码,很多旧系统就会当掉,特别是c语言以0来判断字符串结尾。于是有了所谓的UTF(Unicode Translation Format)标准,即转换格式,它有很多不同的标准如UTF-8、UTF-7、UTF-16(也就是我们的理想),UTF-7的特点是它是一个可变长度字元编码,使用每个字节的7个位,优点是它可以不被防火墙和邮件服务器过滤掉,但缺点也很明显,需要太多的字节来表示。因此我们实际中常用的是UTF-8编码,它使用如下的转换方式
UNICODE UTF-8
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
其中x表示对应的位值,由于我们现在一般用UCS-2标准的UNICODE字符集,因此我们实际上只需要使用前三种格式的编码就可以了,也就是最多转成三个字节。至于UTF-16应该是未来的方向,因为它与内存中的表示是一样的,不过它最大的问题是与以前的旧系统不兼容。
(D)BOM:又来了一个新词,它的全称是(Byte Order Mark),也就是表示数据流采用何种编码以及具体的存储格式的常见的有三种,它是流的头几个字母,你可以
FEFF,就表明这个字节流采用UTF-16编码,并且顺序为Big-Endian,,也就是低地址存放最高有效字节,比较符合阅读分析习惯
FFFE,就表明这个字节流是采用UTF-16编码的,并且顺序为Little-Endian,也就是低地址存放最低有效字节(LSB)
EF BB BF 表明这个字节流是采用UTF-8编码的
而ANSI不存在BOM标识词,当然GB2312也不需要,它采用的是big-endian
所以在windows记事本中,如果分别以四种存储格式来存放“汉”字则有如下字节流,再用二进制分析工具可以看到
ANSI: BA BA
UNICODE: FF FE 49 6C
UNICODE-big endian: FE FF 6C 49
UTF-8: EF BB BF E6 B1 89
同样小结一下,字符编码就是如何将一个字符写入到计算机的存储中。如果你想你的程序可以供多种语言使用,那么应该选择UTF定义的几种编码方式,考虑到与老系统兼容,最好采用UTF-8编码。
三、XML的编码
有了前两个部分说明,我想大家对于字符集和字符编码应该有了一定的了解,不过我们来看看网上经常有人在问为什么我的XML不支持中文。好了,我们来具体看看一个XML文件:
<?xml version="1.0" encoding="gb2312" ?>
这是它的第一行,我们看到有个encoding = "gb2312",那么这个gb2312是一个字符集还是编码方式呢,encoding在英文中的意思是编码,当然它是一个编码方式,那么我们是不是还可以写其它的名字比如"UTF-8"或者省略不写呢。好,我们来看一看XML的解析工具如何来判断你的编码(具体解析工具可能有不同)
(1) 它首先来看 encoding 的值是什么,比如 UTF-8,那么它就用UTF-8的方式来解析
(2) 如果省略掉了这个encoding,那么它会以XML的BOM为识别依据,也就是我们上一小节里提到的BOM字节序,然后根据对应的编码来分析
(3) 同样如果省略掉了encoding,但是XML文件没有BOM开头的字节,那么它就会默认是ANSI的编码,那么对于中文或者其它需要双字节编码的字符就没办识别。因此就会出现XML无法解析的情况
好了,如果你想传一个XML文件给说另一种语言的国家,最好的办法是使用 UTF-8编码,然后在存储的时候使用WideChatToMultiByte以及MultiByteToWideChar将本地代码页的字符串转换成UTF-8的编码,下面给出一个小的c++片断
enum CodePage
{
GB2312=936,
UTF_8=65001
};
template<CodePage TSrc,CodePage TDec>
bool ConvertString(LPCSTR src,LPSTR* dec)
{
wchar_t *buff;
int buffsize = ::MultiByteToWideChar(TSrc,0,src,-1,NULL,0);
buff = (wchar_t*)malloc(sizeof(wchar_t)*(buffsize+1));
::MultiByteToWideChar(TSrc,0,src,-1,buff,buffsize);
int decsize = ::WideCharToMultiByte(TDec,0,buff,-1,0,0,0,0);
*dec = (char*)malloc(sizeof(char)*(decsize+1));
::WideCharToMultiByte(TDec,0,buff,-1,*dec,decsize,0,0);
free(buff);
return true;
}
使用时用
LPSTR outstr;
ConvertString<GB2312,UTF_8>(str,&outstr);
代码写得比较简单,不过可以了解如何从一个GB2312编码字符串转成UTF-8的编码字符串。
如果你采用UTF-8编码,那么无论你使用何种文字,对方都可以正确地看到XML解析后的数据,而不会出乱码。顺便说一句网上有人在问使用TinyXml生成XML,中文出现乱码,其实就是上面所说的原因。
好了,我想经过上面三个部分的解释,你应该对字符集和字符编码以及XML编码应该有了一个比较清晰的认识吧,如果你发现上面所言存在bug或者还有其它高见的欢迎指正。