字符编码那些事儿
转自:https://zhuanlan.zhihu.com/p/26261762 (zhangxiaoyang)
Yang的后花园
32 人赞同了该文章
(欢迎关注 微信订阅号 AND 知乎专栏:Yang的后花园)
你有没有被编码整过?
1 引子
如何将信息交给计算机处理呢?
首先要解决“交”的问题,即让计算机“拿到”这些信息,这个过程便是编码。
有两种很重要的信息:数字、字符。
对于数字来说,编码似乎要简单很多,因为数字天然会有一个二进制的表示。举个栗子,用1个字节存储“5”:5(10) = 101(2) => 0000 0101。
但是,现实也并非那么美好。比如,我们有一个数字“-5”,或者数字“0.5”,或者数字“555555555”,会面临以下问题。
- 如何编码这个数字?(编码方法)
- 使用几个字节编码?(编码长度)
- 会不会有一些数字编码不了?(编码范围)
字符编码,同样有这些问题。
2 字符编码
2.1 字符编码与数字编码
没有数字到二进制转换的天然性,字符编码的思路是先把字符映射到一个数字,然后再将数字映射到存储的二进制。
映射字符到数字的表格,被称作字符集(Character Set),微软称其为代码页(Code Page),又叫内码。字符集里的每一个字符都有一个编号,即映射到的数字,被称作码点(Code Point)。
数字是具有全球性的,大家都认,1就是1,2就是2。但是,字符中的大部分文字却没有这个特性。比如,拿汉字“诗”给老外看,他根本不知道也不关心这是啥意思。所以,就有了针对各种语言的字符集。
-
GB2312、GBK、GB18030 - 中文
-
BIG5 - 中文繁体
-
KS X 1001、EUC-KR、ISO-2022-KR - 韩文
-
JIS X 0208 - 日文
这里就有一个问题了,这些字符集会不会在表示各自文字的时候用到相同的码点?答案是肯定的,这也是乱码的一个原因。
>>> c = '诗'
>>> print c.decode('gbk')
诗
>>> print c.decode('big5')
坅
2.2 ASCII编码
ASCII(American Standard Code for Information Interchange)编码是美国制定的一套字符编码方案,字符集中有0~127共计128个码点,每一个码点都可以使用7位二进制表示。编码方式非常直接,就是码点对应的二进制。
查看一个英文字符“a”对应的ASCII字符集的码点和ASCII编码:
>>> ord(u'a'.encode('ascii'))
97
>>> bin(ord(u'a'.encode('ascii')))
'0b1100001'
因为计算机是按字节分配内存的,尽管ASCII字符集的码点只需要7位,实际上要占用一个字节。所以,有2^8-2^7=128个字符空间是没有用到的。
后来,为了解决部分西欧语言的问题,有人想到利用这剩下的128来表示更多的字符,这些方法统称为EASCII(Extended ASCII),其中著名的有ISO 8859-1(又称作Latin 1)。
2.3 GB XXX编码
对于汉字来说,ASCII剩下的128个字符显然是不够用的。
这难不倒我们,GB2312率先出场。GB2312收录了共7445个图形字符,其中汉字占6763个。显然码点的个数已经超出了1个字节的表示范围,所以使用两个字节来编码一个汉字。
考虑到与ASCII的兼容,规定码点在0~127范围内的字符和ASCII相同,还是1个字节表示1个字符。
>>> ord(u'a'.encode('gb2312'))
97
规定两个码点大于127的字符连在一起时,表示一个汉字。首字节需要在0xA1到 0xF7之间,尾字节在0xA1到0xFE之间,这样可以组合出8000多种码点。
>>> print u'诗'.encode('gb2312')
'\xca\xab'
由于GB2312只收录6763个汉字,还有不少汉字并未有收录。于是微软基于GB2312扩展出GBK。GBK共收录21886个汉字和图形符号,其中汉字21003 个。GBK的首字节在0x81到0xFE 之间,尾字节在0x40到0xFE 之间,一共有2万多个码点。
机智的你有没有猜到,GB是Guo Biao(国标)的意思,K是Kuo Zhan(扩展)的意思…
再后来,GB18030推出,兼容GB2312,基本兼容GBK,共收录汉字70244个。GB18030采用了多字节编码方案,支持更大的编码空间。
3 UNICODE与UTF-XXX
3.1 UNICODE
ASCII作为一个开始,后面出现了各种字符集,各种编码方案。貌似,我们在自己的世界里玩得很好。
事实上,如果把字符集比作是一部字典,那现在的情况是,我的字典里未必有你,你的字典里未必有我。
如果能有一部大而全的字典,我们都用同一个,世界大同,再也不用担心乱码,多好!
于是,UNICODE来了。
UNICODE是一个世界级的字符集,全世界的每一个字符都有唯一的码点。码点具有这样的形式U+[XX]XXXX,其中,X是一个十六进制数字。UNICODE的范围目前是U+0000~U+10FFFF,超过100万。感觉好任性,还有哪个字符没收录进来~
因为码点太多了,为了方便管理,把每65536个码点归为一组,称为一个平面(Plane),共有17个平面。
第一个平面,即Plane 0,又叫做BMP(Basic Multilingual Plane,基本多语言平面),码点范围是U+0000~U+FFFF,日常用到的绝大多数字符都在这个平面。
>>> u'诗'
u'\u8bd7'
参考ASCII的编码方法,UNICODE的编码方法也可以如此直接,即直接存储码点对应的二进制。
额,因为UNICODE的码点最少也要4个字节,所以,意味着曾经在ASCII只需要1个字节的字符们要占用4个字节了,并且,这还和ASCII不兼容。
我们千辛万苦找到UNICODE,却又遇不兼容和占空间的问题,好忧伤啊。
但这也不能怪UNICODE,人家是字符集,只是查个码点,具体如何编码到计算机是不管的。
3.2 UTF-XXX
UTF(Unicode Transformation Format),即UNICODE的码点转换为最终的编码的格式。出于不同的目的,有多种UTF编码方法:UTF-8、UTF-16、UTF-32。
其中,UTF-32便是直接将码点转换为4字节二进制的方法。
考虑到大多数人都在BMP(U+0000~U+FFFF)里玩,只需要使用两个字节编码即可。万一用到了BMP以外的字符,再使用4字节。这便是UTF-16。
由于UTF-16和UTF-32使用多个字节来存储一个数字(码点),所以都需要考虑字节序的问题。
UTF-8不同于UTF-16、UTF-32,UTF-8是一种变长字节的编码方式,无需考虑字节序。对于汉字“诗”,其UTF-8编码计算方式如下。
对于字符“a”,对应的UNICODE码点、UTF-XXX编码为:
CHAR : a
UNICODE: u+0061
UTF-8 : 0x61
UTF-16 : 0x0061
UTF-32 : 0x00000061
对于字符“诗”,对应的UNICODE码点、UTF-XXX编码为:
CHAR : 诗
UNICODE: u+8bd7
UTF-8 : 0xe8af97
UTF-16 : 0x8bd7
UTF-32 : 0x00008bd7
世界还是很美好的,尽管还有一波GBXXX的接口在跑着,不支持4字节UTF-8的MySQL在跑着,老的NodeJS在跑着…
这种感觉就像住在老破小里,看着宏伟的城市蓝图。
不对,是租住(逃
参考
- https://zh.wikipedia.org/wiki/计算机编码
- https://en.wikipedia.org/wiki/Character_encoding
- http://cenalulu.github.io/linux/character-encoding/
- http://www.ruanyifeng.com/blog/2014/12/unicode.html
- http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
- https://www.w3.org/International/questions/qa-what-is-encoding
- https://en.wikipedia.org/wiki/Code_page
- https://zh.wikipedia.org/wiki/代码页
- https://blogs.msdn.microsoft.com/shawnste/2005/03/15/whats-the-difference-between-an-encoding-code-page-character-set-and-unicode/
- https://my.oschina.net/goldenshaw/blog/352859
- https://en.wikipedia.org/wiki/Unicode
- https://my.oschina.net/goldenshaw/blog/310331