字符编码与字符集
我们先弄清几个英文缩写:
ASCII(American Standard Code for Information Interchange):美国标准信息交换代码
ISO (International Organization for Standardization):国际标准化组织
UCS(Universal Multiple-Octet Coded Character Set):通用多八位编码字符集,俗称UNICODE
UTF(UCS Transformation Format):UNICODE格式转换
ANSI (American National Standard Institute):美国国家标准学会
DBCS(Double Byte Charecter Set):双字节字符集
人可以直接使用汉字,但计算机只认得0和1的二进制代码。
编码:就是把汉字跟0和1的二进制代码组合进行一一对应的过程。因为0和1的组合方式非常多,所以你可以编码,别人也可以编,这就带来了另一个概念↓
字符集:就是由权威机构进行编码而形成的编码集合就叫字符集。例如:中文的字符集GB2312就是中国政府自己编出来的;台湾当然不服,所以他们也自己编码(大名鼎鼎的BIG5)
字符集的根本就是编码的方法,大家各自的编码方法都不相同,这对电脑的普及和互联造成了非常大的困扰。这时美国老大站出来推出了一种 双字节编码的标准叫做:ANSI!中国的GB2312就符合ANSI的标准。
中国的ANSI是GB2312,日本的ANSI则是JIS;中文的ANSI编码只认中国字,日本的一样只认日本字。所以这种编码标准虽然统一了编码方法,但没有解决不同字符集的兼容问题。
这时就出现了Unicode编码,一个各种语言通吃的编码。当然这种编码如此强大也是付出了代价的,那就是长度!
字符编码的发展过程:
ASCII --> GB2312 --> GBK --> GB18030 --> Unicode
在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0)。
为了方便多台计算机之间的通信,我们需要相互之间约定统一的编码,通过这个统一的编码将信息转换成相应的二进制码,再在另外的机器上按照这个编码方式进行解码,就显示出正确的文字信息,这样计算机之间就可以相互读懂信息,并准确的显示了。
在计算机出现的初期,ANSI(美国国家标准学会)制定了ASCII编码,它是标准的单字节字符编码方案,它最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,后被ISO(国际标准化组织)定为国际标准,称为ISO 646标准。适用于所有拉丁文字字母。
ASCII使用的是单字节编码,1byte=8bit,起初只用了1个字节的前面7位进行编码,共编了127个字符(27=128),包括所有的空格、标点符号、数字、大小写字母和一些控制符。
但随着计算机在欧洲的普及,很多国家用的字符不是英文字符,如:αβγ,所以它们对ASCII对应的字符集进行了扩展,一直编到了255位(28=256),也就是扩展的ASCII码。
等到计算机传到了中国,我们发现汉字太多了,用一个字节来编码存储的话,根本不够用,于是我们搞出了GB2312编码。
GB2312规定:采用两个字节进行编码,原有的ASCII码中,小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字。前面的一个字节(称之为高字节)从0xB0用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
但是中文不仅有简体汉字,还有繁体,为了表示这些繁体汉字,我们对GB2312进行了扩展,形成了GBK编码。
后来,我们发现少数民族的语言我们无法显示,于是我们又对GBK进行了扩展,形成了GB18030。
从ASCII、GB2312、到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。
利用双字节进行编码的标准,我们称之为DBCS(双字节字符集)。
DBCS最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。
各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,非常不方便相互之间的通讯和软件的通用,比如就连中文的编码,中国(GBK)、台湾(BIG5)、香港(HKSCS)各自都有自己的一套。
这时,ISO(国际标谁化组织)为了解决这一问题推出了UNICODE编码,统一了地球上所有的字符编码。 ISO规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些"半角"字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。所以在UNICODE码下,英文字符也是占两个字节的。
Unicode诞生后有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。
以目前常用的UCS-2为例,它可以表示的字符数为2^16=65535,基本上可以容纳所有的欧美字符和绝大部分的亚洲字符。
在Unicode里,所有的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,现在的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的解决了。
但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。
我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了。
另一个更加严重的问题是,C语言使用'/0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉。
于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是因为它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF(UCS转换格式)。
它是将Unicode编码规则和计算机的实际编码对应起来的一个规则。现在流行的UTF有2种:UTF-8和UTF-16。
UTF-8以字节为单位对Unicode进行编码,即每8位为单元对UCS进行编码。从Unicode到UTF-8的编码方式如下:
Unicode编码(十六进制) |
UTF-8 字节流(二进制) 编码模板 |
000000 - 00007F |
0xxxxxxx |
000080 - 0007FF |
110xxxxx 10xxxxxx |
000800 - 00FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
010000 - 10FFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
这种编码规则可以和ASCII编码保持最大程度的兼容
在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库可以继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类可以对UTF编码进行处理。
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
参考资料:
http://www.cnblogs.com/uuhua/archive/2010/06/15/1758552.html (编码与字符集)
http://blog.csdn.net/zzjjian333/article/details/8203443 (编码与字符集)
http://blog.csdn.net/xiongxiao/article/details/3741731 (UNICODE,GBK,UTF-8)
小知识:
Windows操作系统下可以方便的对字符的UNICODE码进行转换。
在Microsoft Word或者金山WPS之下,按下Alt键不放,输入 0 和某个字符的 Unicode 编码(十进制),再松开 Alt 键即可得到该字符,如Alt + 033865会得到Unicode字符“叶”(繁体)。另外按Alt + X 组合键,MS Word 也会将光标前面的字符同其十六进制的四位 Unicode编码进行互相转换。
要得到字符的UNICODE 10进制码,可以通过在线编码转换工具进行转换。
http://tool.oschina.net/encode
所以,[Alt] + 025105 -> 我 (按住[Alt],再输入025105就可以出现“我”字了)
同样,我们在“我”字前按住[Alt] + x,就得到了UNICODE的16进制编码:6211。同样的方式也可将6211转换成“我”。
当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!
为什么会这样呢?
分析:
这时我们看看utf8的编码格式:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
在保存这个操作中,windows默认保存的编码是ANSI(实际上应该是GBK)。
这样联通这两个字的二进制内码是:(一个字占两个字节)
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
巧合的地方在于联通这两个字的ANSI编码符合utf8编码的第二个模板。
第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",不好意思,这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。
(http://ruowu.iteye.com/blog/734746)