读书人

资料编码入门:UTF-8和GB

发布时间: 2012-07-23 09:42:19 作者: rapoo

文件编码入门:UTF-8和GB
在简体中文Windows系统中:

1.打开记事本,输入“移动”,保存关闭后重新打开,显示的仍然是“移动”两个字。

2.重新新建一个文本文件,输入“联通”,保存关闭后重新打开,显示的就不是“联通”字符了,而是看上去所谓的乱码。

的确,这就是一个编码问题。

编码问题由来

ASCII

字符需要编码,一套编码体系就形成了一个字符集。美国人最开始只创造了一个字符集,也就是ASCII字符集,ASCII字符集,长8位,首位为0。后来欧洲国家发现128个字符不够用,想利用ASCII后128位,128位还是满足不了所有欧洲国家的要求,就对后128个字符进行分片,形成了iso-8859系列字符集,包括iso-8859-1,iso-8859-2等。

GB2312,GBK和GB18030

计算机来到中国后,又催生了GB2312编码标准,GB2312没有包括繁体字,后又扩展成为GBK(GB13000),GBK是GB2312的“超集”。GB2312和GBK编码标准中,存储方法兼容ASCII,汉字占用两个字节。2000年和2005年又发布了GB18030-2000和GB18030-2005编码标准,存储方法中有单字节、双字节和四字节三种方式对字符编码进行存储。平时说的ANSI编码,都是根据不同的国家和地区而不同的标准。在简体中文系统下,ANSI?编码代表?GBK?编码,在日文操作系统下,ANSI?编码代表?JIS?编码。?不同?ANSI?编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段?ANSI?编码的文本中。

Unicode

历史介绍省略数百字。。。

Unicode基于通用字符集(Universal?Character?Set)的标准来发展,是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。

?

(UCS-2用两个字节编码,?UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行?(row),每行有256个码位(cell)。group?0的平面0被称作BMP(Basic?Multilingual?Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。每个平面有2^16=65536个码位。)

Unicode?&?GBXXX?

Unicode和GBXXX是两套不同的编码标准,字符的码位不相同,如果需要转换,必须要同时知道一个字符在两个编码中的码位。

编码简介

UTF-8

Unicode是编码标准,并没有规定字符的存储方式。UTF-8、UTF-16、UTF-32都是将Unicode标准中的码位转换到具体存储数据的方案。总之,任何一个编码标准和具体的字符存储方案是分离的,只要存储后的编码还能映射到原始的编码标准中的码位。

为什么不直接用Unicode的UCS-2码位来直接当作字符存储的数据编码呢,一是为了考虑和ASCII的兼容性,二是对属于ASCII的字符用Unicode编码太占用空间。UTF-8就是在这样的情况下诞生了。UTF-8只是一种编码的存储方案,从一个字符的UTF-8编码可以找到唯一对应的Unicode码位。

简单介绍下UTF-8,UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

?

  Unicode编码(16进制)  UTF-8?字节流(二进制)

  000000?-?00007F   0xxxxxxx

  000080?-?0007FF  110xxxxx?10xxxxxx

  000800?-?00FFFF   1110xxxx?10xxxxxx?10xxxxxx

  010000?-?10FFFF   11110xxx?10xxxxxx?10xxxxxx?10xxxxxx

?

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

例:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx?10xxxxxx?10xxxxxx。将0x6C49写成二进制是:0110?1100?0100?1001,?用这个比特流依次代替模板中的x,得到:11100110?10110001?10001001,即E6?B1?89。

判断一个文本编码是否是UTF-8编码的最简单的正则:

/^([\x01-\x7f]|[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/

上述正则并不是100%能够判断正确的。事实上也没有100%正确的方法。一个Bad Case就是文章开头提到的“联通”例子,“联通”的GBK编码是c1?aa?cd?a8,刚好符合了上述的正则表达式,被Windows记事本认为是UTF-8来编码了。

联通的Bad Case还不是最Bad的,假如c1?aa?cd?a8是UTF-8编码,c1?aa对应到Unicode的十六进制表示是6A(字符j),只需要一个字节就可以表示了。所以c1?aa不是一个规范的UTF-8编码,虽然c1?aa可以通过简单的正则表达式验证。

下面是一个更Bad的Case,记事本输入“伞”,关闭后再打开,就变成拉丁字符了,对于这样一个文本文件,如果不额外告诉程序是什么编码的,程序当UTF-8和当GBK来处理都不是程序的错了。

那么哪些汉字会引起当作UTF-8来误读的情况呢?根据正则表达式和GBK的编码情况(全国信息技术标准化技术委员会汉字内码扩展规范(GBK)码:http://www.snwei.com/studypc/write/009.htm?),在[\xc0-\xdf][\x80-\xbf]之间的GBK编码的汉字都可能会有问题,只要GBK编码的文本文件中的字符都是落在这个范围内(包括有任意的其它ASCII字符的情况,比如“伞1”也会被当作UTF-8来处理),都会出现编码难以或者不能判断的情况。

UTF-8的编码就介绍到这儿,没有什么准确判断UTF-8编码的方法,isUTF8Encode这样的函数如果返回值是二值的,都是有例外的。到可以写个判断肯定不是UTF-8编码的方法。

BOM

BOM——Byte?Order?Mark,中文名译作“字节顺序标记”。在UCS?编码中有一个叫做?"Zero?Width?No-Break?Space"?,中文译名作“零宽无间断间隔”的字符,它的编码是USC编码是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?来表明编码方式(不知道是不是MS干的)。字符?"Zero?Width?No-Break?Space"?的?UTF-8?编码是?EF?BB?BF。所以如果接收者收到以?EF?BB?BF?开头的字节流,就知道这是?UTF-8编码了。Windows?就是使用?BOM?来标记文本文件的编码方式的。所以UTF-8一般有两种,一个是UTF-8?with?BOM,一个是UTF-8?without?BOM。linux下的非常多的程序都是不认UTF-8?with?BOM的,另外PHP也是不认的,只要有BOM的UTF-8,PHP(最新版本是否支持BOM未验证)就会出错。

不过用BOM也会有Bad?Case,“锘胯创”三个字在记事本中以GBK保存后再打开就变成了UTF-8的“贴”。不要在UTF-8中保存BOM头。如果在程序的输出中看到字符“锘”就要格外注意,很有可能是UTF-8?with?BOM的编码被当作GBK的来处理了。

小结

个人认为GBK的主要优势在于Web页面和减少磁盘IO,GBK能够节省实实在在的带宽,假设一个web请求可以省下100个字节,那么100万/秒的访问下,能够节省100MB/s的带宽了。也许服务器可以支撑更大规模的请求访问,处理更多的数据,但是带宽不见得够用。

对于输入输出中有字符串的,最好都明确告知编码。程序自身内部的编码可以统一,而在输入输出的时候按要求进行转码。

?

参考文献

http://www.unicode.org/charts/?

http://baike.baidu.com/view/40801.htm

http://www.hudong.com/wiki/gbk

读书人网 >操作系统

热点推荐