中文编码基础知识
Unicode编码介绍
首先,全世界的各种文字的编码加起来也不多,目前统计来看也就不到20w个,如果按照位数来看的话,而2^16次方等于65536,也就说说用18个bit肯定能将所有字符都表示出来。
而全世界的字符绝大部分字符都是东南亚的象形文字导致的,其中东南亚字符占据了大概将近70%个字符。
而Unicode编码出现的背景就是Uni
, 目的就是要一统全世界的编码,让计算机只需要装一套字符集,就可以解析全世界所有的语言。
Unicode编码的设计就是这样的,他是一个逐步收集字符并扩展的字符集,其将每65536(即2^16)次个字符集定义为一个平面,然后到目前为止,其一共建造了17个平面了。
也就是说用21位(3个字节)之内能表示全世界所有的字符。
注意,其实目前总量也就收集了10w多个字符,按道理18位肯定能存储下来了,主要是考虑到字符集的可扩展还留下了不少空间用于一些特定字符集的扩展。
UTF-32 && UTF-8 && UTF-16
Unicode只是定义了一个字符集,以及字符集对应的编码数字,但是这个编码标准具体对应到计算机怎么存储,就是具体的各个编码方式来实现了。
UTF-32
下面我们以UTF-32来举例说明一下:
UTF-32编码是一个最简单也最保险的编码,就是将所有字符都按照32位的方式(4个字节)保存下来,也就是说即使最常见的英文的a,b,c,d之类的都需要占用4个字节,而其他编码都只占1个字节甚至更少。
这种情况下,想要推广Unicode编码方式或者具体的UTF-32,几乎在英文的国家是不太可能了,事实也是如此,现在也确实没人用这个编码。
但是这个编码也不是没好处的,好处就是查询性能快,不需要额外的一些判断工作,使得其可以很快的加载。
UTF-8
那么什么是UTF-8编码呢,顾名思义,最小的编码的位数是8位,那么这种场景下针对一般的拉丁语系国家就能推广了,对于他们最常见的这些字符都是一样的,还能兼容查看别的国家的语言,何乐而不为呢?
那么具体UTF-8的编码是如何实现的呢?下面就是其编码表:
Unicode范围 | UTF-8编码方式 | 一共多少个字符 | 自由位数 |
---|---|---|---|
0000 0000 - 0000 007F | 0xxx xxxx | 128个 | 7位 |
0000 0080 - 0000 07FF | 110x xxxx, 10xx xxxx | 1920个 | 11位(即7FF) |
0000 0800 - 0000 FFFF | 1110 xxxx, 10xx xxxx, 10xx xxxx | 65535个 | 16位(即7FF) |
0001 0000 - 0010 FFFF | 1111 0xxx, 10xx xxxx, 10xx xxxx, 10xx xxxx | ?个 | 21位(即7FF) |
如上表,就是将每个字节的前几个字节码,作为特殊标志符号用来做编码的判断,当解码的时候只需要做如下判断:
- 当该字节第一位是0,那么就是一个单字节编码,直接将后面的值拿来当Unicode就好;
- 当字节第一位是1,且第二位是0,那么该字节一定是一个多字节编码的一部分,且一定不是首字节编码;
- 当字节第一位是1,且第二位是1,那么该字节一定是一个多字节编码的一部分,且是首字节,而且只需要数前面有几个1,就知道后面有几个字节是同一个字符的编码了;
好了,原理基本搞清楚了,我们就来看一个例子,严
的编码为4E25(0100 1110 0010 0101),该Unicode编码查询上表的第一列,发现其位于第三行当中,那么我们只需要将二进制编码按照第二列的格式依次放进x
即可,编码出来的UTF-8二进制码为 1110 0100 1011 1000 1010 0101,十六进制为E4B8A5。
该编码设计也是有一定的技巧,编码转换效率也挺高的,所以目前UTF-8编码成为了目前世界上最流行的编码。
最后我们再来聊聊UTF-16,UTF-16意思是说我最小的编码位数为16,其编码复杂度要比UTF-8要简单,但是比UTF-32要复杂一点;占据空间比UTF-8要大,但是比UTF-32要小,算是一种折衷的编码方式,但是目前最流行的依然是UTF-8,也就是说现在来看,空间重要性远远要大于性能。
UTF-16
在UTF-16中,就很简单,基本平面(即Unicode编码为0~2^16-1)的这一段编码都用两个字节来表示,剩下的都用4个字节来表示。
但是有一个问题,UTF-8有通过数1的个数来判断我后面跟着几个字节,那么UTF-16是根据什么来判断我后面是2个字节还是4个字节呢?
这里是利用了Unicode编码的一些"设计",在Unicode编码中,在基本平面当中有一段是空的编码,即不可能存在这样的编码,即D800~DFFF,大概2048个位置。
那么在非基本平面的case中,就将前10位编码做一下转换,将编码做一下加减法映射到D800~DFFF当中,那么只要发现前10位编码在这个范围之内的字节,一定就是4个字节的编码。
ASCII码
这个编码其实是最早的编码,也是英文系的作者制作的编码。因为英文系的字符集很简单,至于几十个可见字符,再加上一部分不可见字符(比如换行,换页)之类的,总共也不到100个。
所以ASCII码是最早的字节编码,其只包含一个字节,而且都没用完,只用了7个bit就够了,第8个bit是默认留空位0的,针对英文系的语言已经没问题了。
但是其最大的问题,也是不能针对其他语言做扩展,比如针对欧洲同样的拉丁语系,有的就需要在字母上面加"声母"来做,曾经一度比较混乱的,很多语言都在ASCII码的基础上做了扩展,导致比如十进制为213的字符编码,在法语中代表的是一个字母,在德语或者俄语中又代表的是另外一个字母。
而由于互联网的普及,直接导致了需要在同一台电脑上显示不同国家的语言,于是就催生了Unicode编码的诞生。
GB2312 && GBK && GB18030
这几个编码都是我们中国人民自己研究出来的编码方式,跟Unicode完全独立的两套编码方式。
首先出现的是GB2312,其跟ASCII码兼容,收录了不到7500个汉字和中文编码,具体而言分成了94个区,每个区又分成了94个字符。
每个字符都是用2个字节来表示,也就是说最多可以表示65536个字符。
然后就发现,字符集只有7500个不够呀,发现人家Unicode都收集了大概2w个字符,所以就又扩展了一个版本,叫GBK。
GBK版本跟GB2312是完全兼容的,只是又新增了一些位来表示剩下的没入库的字符,但是最多是65536个字符,也是两个字节。
不过再往后发展的时候,发现GBK也不够用了,所以就又发明了GB18030字符编码,该编码跟UTF-8类似,是一个变长存储的字符编码,分别为1、2、4个字节编码。可以存储上百万个字符集,就再也无忧了。
其他一些小知识点
- CJK是什么?
China、Japan、Korean的简称,这并不是一个编码,而是将中日韩的文字中重复的部分给做一下公因式提取,放到同一个UniCode的码点上面去,免得浪费。 - ANSI编码是什么?
这个是Windows国际化做的一个编码,其实并不是一个具体的编码,只是一个国家到编码的映射。比如针对中文语言,映射的默认就是GBK编码。 - Big5编码是什么?
这个是台湾同胞针对繁体中文做的一个编码方式。 - javascript的编码方式什么?
js这门语言编码都是用的Unicode,具体的编码方式是不完整的UTF-16。即只有两个字节的UTF-16,也叫ASC2。
所以js在处于非常见字符的时候,会出bug,需要自己再单独处理一下4个字节的方式,具体处理方式在UTF-16的编码方式中已经介绍过了,不过在ES6中已经基本解决该问题了。
但是为了兼容性,还是留得有一些遗留问题的,比如string.length函数还是按照2个字节来处理的,如果要获取正确的length,需要用Array.from(str).length就能正确获取了。
或者如果要遍历的话,用 for(let ch of str) 也可以正确的遍历。
js中对应了几个新的函数: - codePointAt
获取第几个字符的CodePoint - String.fromCodePoint
将CodePoint转换为文字 - normalize
用来将两种不同编码方式的Unicode给归一化,使得其对应的字符串是相等的,主要是针对有声母这种场景,是单独弄一个字符,还是用原始字符加声量字符一起来作为字符编码,如下:
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
参考
- Unicode和javascript的关系详解. http://www.ruanyifeng.com/blog/2014/12/unicode.html
- 字符集的编码方式详解. http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
- GB系列编码解读. https://www.cnblogs.com/malecrab/p/5300497.html