区位码是早些年(1980)中国制定的一个编码标准,如果有玩过小霸王学习机的话,应该会记得有个叫做“区位”的输入法(没记错的话是按F4选择)。就是打四个数字然后就出来汉字了,什么原理呢。请看下面的区位码表,每一个字符都有对应一个编号。其中前两位为“区”,后两位为“位”,中文汉字的编号区号是从16开始的,位号从1开始。前面的区号有一些符号、数字、字母、注音符号(台)、制表符、日文等等。
而GB2312编码就是基于区位码的,用双字节编码表示中文和中文符号。一般编码方式是:0xA0+区号,0xA0+位号。如下表中的“安”,区位号是1618(十进制),那么“安”字的GB2312编码就是0xA0+160xA0+18也就是0xB00xB2。根据区位码表,GB2312的汉字编码范围是0xB0A1~0xF7FE
通过上面的编码,可能大家注意到了,区位码里有英文和数字,按道理说是不是也应该是双字节的呢。而一般情况下,我们见到的英文和数字是单字节的,以ASCII编码,也就是说现代的GBK编码是兼容ASCII编码的。比如一个数字2,对应的二进制是0x32,而不是0xA30xB2。那么问题来了,0xA30xB2又对应到什么呢?还是2(笑)。注意看了,这里的2跟2是不是有点不太一样?!确实是不一样的。这里的双字节2是全角的二,ASCII的2是半角的二,一般输入法里的切换全角半角就是这里不同。
如果留意过早些年的手机(功能机),会发现人名中常见的“燊”字是打不出来的。为什么呢?因为早期的区位码表里面并没有这些字,也就是说早期的GB2312也是没有这些字的。到后来的GBK(1995)才补充了大量的汉字进去,当然现在的安卓苹果应该都是GBK字库了。再看看这些补充的汉字的字节码燊0x9F0xF6。和前面说到的GB2312不同,有的字的编码比0xA00xA0还小,难道新补充的区位号还能是负的??其实不然,这次的补充只补充了计算机编码表,并没有补充区位码表。也就是说区位码表并没有更新,用区位码打字法还是打不出这些字,而网上的反向区位码表查询也只是按照GBK的编码计算,并不代表字与区位号完全对应。时代的发展,区位码表早已经是进入博物馆的东西了。
补充:UTF8编码与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字节模板1110yyyy10yyyyxx10xxxxxx。将535A写成二进制是:0101001101011010,高八位分别代替y,低八位分别代替x,得到111001011000110110011010,也就是0xE58D9A,这就是博字的UTF8编码。
前面提到,GBK的编码里英文字符有全角和半角之分,全角为GBK的标准编码过的双字节2,半角为ASCII的单字节2。那现在UTF8是全部用一个公式,理论上只有半角的2的,怎么支持全角的2呢?哈哈,结果是Unicode为中国特色的全角英文字符也单独分配了编码,简单粗暴。比如全角的2的Unicode编码是\uFF12,转换到UTF8就是0xEFBC92。
那么这些库是怎么区分这些编码的呢?那就是词库,你会看到库的源码里有大量的数组,其实就是对应一个编码里的常见词组编码组合。同样的文件字节流在一个词组库里的匹配程度越高,就越有可能是该编码,判断的准确率就越大。而文件中的中文越少越零散,判断的准确率就越低。
文中多次提及ASCII编码,其实这应该是每个程序员都非常熟悉、认真了解的东西。对于嵌入式开发的人来说,应该能随时在字符与ASCII码中转换,就像十六进制与二进制之间的转换一样。标准ASCII是128个,范围是0x00~0x7F(00000000~01110000),最高位为0。也有一个扩展ASCII码规则,把最高位也用上了,变成256个,但是这个扩展标准争议很大,没有得到推广,应该以后不会得到推广。因为无论是GBK还是UTF8,如果ASCII字符编码最高位能为1都会造成混乱无法解析。
以GBK为例,如果ASCII的字符最高位也能是1,那么是应该截取一个解析为ASCII呢?还是截取两个解析为中文字符?这根本无法判断。UTF8也是同理,遇到0xxx开头则截取一个(即为标准ASCII),遇到110x开头则截取两个,遇到1110开头则截取三个,如果ASCII包含1开头的,则无法确定何时截取多少个。
在哪里还能一睹扩展ASCII的真容呢?其实很简单,只要把网页的meta改成ASCII就行了