彻底搞懂编码GBK和UTF8
常⽤编码格式⼀览
⾸先来看⼀下常⽤的编码有哪些,截图⾃Notepad++。其中ANSI在中国⼤陆即为GBK(以前是GB2312),最常⽤的是 GBK 和 UTF8⽆BOM 编码格式。后⾯三个都是有BOM头的⽂本格式,UCS-2即为⼈们常说的Unicode编码,⼜分为⼤端、⼩端。
所谓BOM头(Byte Order Mark)就是⽂本⽂件中开始的⼏个并不表⽰任何字符的字节,⽤⼆进制编辑器(如bz.exe)就能看到了。
1. UTF8的BOM头为 0xEF 0xBB 0xBF
2. Unicode⼤端模式为 0xFE 0xFF
3. Unicode⼩端模式为 0xFF 0xFE
何为GBK,何为GB2312,与区位码有何渊源?
区位码是早些年(1980)中国制定的⼀个编码标准,如果有玩过⼩霸王学习机的话,应该会记得有个叫做“区位”的输⼊法(没记错的话是按F4选择)。就是打四个数字然后就出来汉字了,什么原理呢。请看下⾯的区位码表,每⼀个字符都有对应⼀个编号。其中前两位
为“区”,后两位为“位”,中⽂汉字的编号区号是从16开始的,位号从1开始。前⾯的区号有⼀些符号、数字、字母、注⾳符号(台)、制表符、⽇⽂等等。
⽽GB2312编码就是基于区位码的,⽤双字节编码表⽰中⽂和中⽂符号。⼀般编码⽅式是:0xA0+区号,0xA0+位号。如下表中的“安”,区位号是1618(⼗进制),那么“安”字的GB2312编码就是 0xA0+16 0xA0+18 也就是 0xB0 0xB2 。根据区位码
表,GB2312的汉字编码范围是0xB0A1~0xF7FE
可能⼤家注意到了,区位码⾥有英⽂和数字,按道理说是不是也应该是双字节的呢。⽽⼀般情况下,我们见到的英⽂和数字是单字节的,以ASCII编码,也就是说现代的GBK编码是兼容ASCII编码的。⽐如⼀个数字2,对应的⼆进制是0x32,⽽不是 0xA3 0xB2。那么问题来了,0xA3 0xB2 ⼜对应到什么呢?还是2(笑)。注意看了,这⾥的2跟2是不是有点不太⼀样?!确实是不⼀样的。这⾥的双字节2是全⾓的⼆,ASCII的2是半⾓的⼆,⼀般输⼊法⾥的切换全⾓半⾓就是这⾥不同。
如果留意过早些年的⼿机(功能机),会发现⼈名中常见的“燊”字是打不出来的。为什么呢?因为早期
的区位码表⾥⾯并没有这些字,也就是说早期的GB2312也是没有这些字的。到后来的GBK(1995)才补充了⼤量的汉字进去,当然现在的安卓苹果应该都是GBK字库了。再看看这些补充的汉字的字节码 燊 0x9F 0xF6 。和前⾯说到的GB2312不同,有的字的编码⽐ 0xA0 0xA0 还⼩,难道新补充的区位号还能是负的??其实不然,这次的补充只补充了计算机编码表,并没有补充区位码表。也就是说区位码表并没有更新,⽤区位码打字法还是打不出这些字,⽽⽹上的反向区位码表查询也只是按照GBK的编码计算,并不代表字与区位号完全对应。时代的发展,区位码表早已经是进⼊博物馆的东西了。繁体字转换器
Big5是与GB2312同时期的⼀种台湾地区繁体字的编码格式。后来GBK编码的制定,把Big5⽤的繁体字也包含进来(但编码不兼容),还增加了⼀些其它的中⽂字符。细⼼的朋友可能还会发现,台湾⾹港⽤的繁体字(如KTV⾥的字幕)跟⼤陆⽤的繁体字还有点笔画上的不⼀样,其实这跟编码⽆关,是字体的不同,⼤陆⼀般⽤的是宋体楷体⿊体,港澳台常⽤的是明体(⽤的是新細明體)。GBK总体编码范围
为0x8140~0xFEFE,⾸字节在 0x81~0xFE 之间,尾字节在 0x40~0xFE 之间,剔除 xx7F ⼀条线。详细编码表可以。微软Windows 安排给GBK的code page(代码页)是CP936,所以有时候看到编码格式是CP936,其实就是GBK的意思。2000年和2005年,国家⼜先后两次发布了GB18030编码标准,兼容GBK,新增四字节的编码,但⽐较少见。
同⼀个编码⽂件⾥,怎么区分ASCII和中⽂编码呢?从我们知道标准ASCII只有128个字符,0~127即0x00~0x7F(0111 1111)。所以区分的⽅法就是,⾼字节的最⾼位为0则为ASCII,为1则为中⽂。
UTF8编码与 Unicode编码
GBK是中国标准,只在中国使⽤,并没有表⽰⼤多数其它国家的编码;⽽各国⼜陆续推出各⾃的编码标准,互不兼容,⾮常不利于全球化发展。于是后来国际组织发⾏了⼀个全球统⼀编码表,把全球各国⽂字都统⼀在⼀个编码标准⾥,名为Unicode。很多⼈都很疑惑,到底UTF8与Unicode两者有什么关系?如果要类⽐的话,UTF8相当于GB2312,Unicode相当于区位码表,不同的是它们之间的编号范围和转换公式。那什么是原始的Unicode编码呢?如果你⽤过PHP的话,json_encode函数默认会把中⽂编码成为Unicode,⽐如“⾸发于博客园”就会转码成“\u9996\u53d1\u4e8e\u535a\u5ba2\u56ed”。可以看到每个字都变成了 \uXXXX 的形式,这个就是⽂字的对应Unicode编码,\u表⽰Unicode的意思,⽹上也有⽤U+表⽰unicode。现⾏的Unicode编码标准⾥,绝⼤多数程序语⾔只⽀持双字节。英⽂字母、标点也收纳在Unicode编码中。有兴趣的可以在⾥尝试“中⽂转Unicode”,可以得到你输⼊⽂字的Unicode编码。
因为英⽂字符也全部使⽤双字节,存储成本和流量会⼤⼤地增加,所以Unicode编码⼤多数情况并没有被原始地使⽤,⽽是被转换编码成UTF8。下表就是其转换公式:
第⼀种:Unicode从 0x0000 到 0x007F 范围的,是不是有点熟悉?对,其实就是标准ASCII码⾥⾯的内容,所以直接去掉前⾯那个字节0x00,使⽤其第⼆个字节(与ASCII码相同)作为其编码,即为单字节UTF8。
第⼆种:Unicode从 0x0080 到 0x07FF 范围的,转换成双字节UTF8。
第三种:Unicode从 0x8000 到 0xFFFF 范围的,转换成三字节UTF8,⼀般中⽂都是在这个范围⾥。
第四种:超过双字节的Unicode⽬前还没有⼴泛⽀持,仅见emoji表情在此范围。
例如“博”字的Unicode编码是\u535a。0x535A在0x0800~0xFFFF之间,所以⽤3字节模板 1110yyyy 10
yyyyxx 10xxxxxx。将535A写成⼆进制是:0101 0011 0101 1010,⾼⼋位分别代替y,低⼋位分别代替x,得到 11100101 10001101 10011010,也就是 0xE58D9A ,这就是博字的UTF8编码。
前⾯提到,GBK的编码⾥英⽂字符有全⾓和半⾓之分,全⾓为GBK的标准编码过的双字节2,半⾓为ASCII的单字节2。那现在UTF8是全部⽤⼀个公式,理论上只有半⾓的2的,怎么⽀持全⾓的2呢?哈哈,结果是Unicode为中国特⾊的全⾓英⽂字符也单独分配了编码,简单粗暴。⽐如全⾓的2的Unicode编码是 \uFF12,转换到UTF8就是 0xEFBC92。
⽂章开头有说到 UCS-2,其实UCS-2就是原始的双字节Unicode编码,⽤⼆进制编辑器打开UCS-2⼤端模式的⽂本⽂件,从左往右看,看到的就是每个字符的Unicode编码了。⾄于什么是⼤端⼩端,就是字节的存放顺序不同,这⼀般是嵌⼊式编程的范畴。
如何区分⼀个⽂本是⽆BOM的UTF8还是GBK
前⾯说到的⼏种编码,其中有的是有BOM头的,可以直接根据BOM头区分出其编码。有两个是没有BOM头的,UTF8和GBK,那么两者怎么区分呢?答案是,只能按⼤量的编码分析来区分。⽬前识别准确率很⾼的有:Notepad++等⼀些常⽤的IDE,PHP的mb_系列函
数,python的chardet库及其它语⾔衍⽣版如jchardet,等(请⾃⾏github)。
那么这些库是怎么区分这些编码的呢?那就是词库,你会看到库的源码⾥有⼤量的数组,其实就是对应⼀个编码⾥的常见词组编码组合。同样的⽂件字节流在⼀个词组库⾥的匹配程度越⾼,就越有可能是该编码,判断的准确率就越⼤。⽽⽂件中的中⽂越少越零散,判断的准确率就越低。
关于ASCII
⽂中多次提及ASCII编码,其实这应该是每个程序员都⾮常熟悉、认真了解的东西。对于嵌⼊式开发的⼈来说,应该能随时在字符与ASCII 码中转换,就像⼗六进制与⼆进制之间的转换⼀样。标准ASCII是128个,范围是0x00~0x7F (0000 0000~0111 0000) ,最⾼位为0。也有⼀个扩展ASCII码规则,把最⾼位也⽤上了,变成256个,但是这个扩展标准争议很⼤,没有得到推⼴,应该以后不会得到推⼴。因为⽆论是GBK还是UTF8,如果ASCII字符编码最⾼位能为1都会造成混乱⽆法解析。
以GBK为例,如果ASCII的字符最⾼位也能是1,那么是应该截取⼀个解析为ASCII呢?还是截取两个解析为中⽂字符?这根本⽆法判断。UTF8也是同理,遇到 0xxx 开头则截取⼀个(即为标准ASCII), 遇到 110x 开头则截取两个,遇到 1110 开头则截取三个,如果ASCII 包含1开头的,则⽆法确定何时截取多少个。
在哪⾥还能⼀睹扩展ASCII的真容呢?其实很简单,只要把⽹页的meta改成ASCII就⾏了 <meta charset="ASCII"/> 。⼜或者浏览器的编码选择“西⽅”,即可见到与平常所见不同的乱码。(截图为⽕
狐)
姊妹篇:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论