文件编码入门:UTF-8和GB

移动比联通强?

在简体中文Windows系统中:

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

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

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

编码问题由来

ASCII

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

GB2312,GBKGB18030

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

Unicode

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

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

 

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

Unicode & GBXXX?

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

编码简介

UTF-8

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

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

简单介绍下UTF-8UTF-8以字节为单位对Unicode进行编码。从UnicodeUTF-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字节模板有21x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

例:“汉”字的Unicode编码是0x6C490x6C490x0800-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 a8UTF-8编码,c1 aa对应到Unicode的十六进制表示是6A(字符j),只需要一个字节就可以表示了。所以c1 aa不是一个规范的UTF-8编码,虽然c1 aa可以通过简单的正则表达式验证。

下面是一个更BadCase,记事本输入“伞”,关闭后再打开,就变成拉丁字符ɡ了,对于这样一个文本文件,如果不额外告诉程序是什么编码的,程序当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 BOMlinux下的非常多的程序都是不认UTF-8 with BOM的,另外PHP也是不认的,只要有BOMUTF-8PHP(最新版本是否支持BOM未验证)就会出错。

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

小结

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

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

 

参考文献

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值