And the Lord spake, saying, "First shalt thou take out the Holy Pin. Then, shalt thou count to three. No more. No less. Three shalt be the number thou shalt count, and the number of the counting shall be three. Four shalt thou not count, nor either count thou two, excepting that thou then proceed to three. Five is right out. Once the number three, being the third number, be reached, then, lobbest thou thy Holy Hand Grenade of Antioch towards thy foe, who, being naughty in My sight, shall snuff it."上帝说:『首先取下栓,而后很少很多数到三。应该数到三,你数到的数字是三。你除了数到三,既不要数到四,也不要数到二,五是数多了。「三」一旦被数到,成为被数到的第三个数字,就高高的向敌人扔出安提拉之神圣手榴弹,阿门。』
—— 巨蟒与圣杯 Monty Python and the Holy Grail (1975)less
UTF-8
的规范里充斥着这样神秘的句子:“第一个位元组由110
开始,接着的位元组由10
开始”,“第一个位元组由1110
开始,接着的位元组由10
开始”。spa
那么这究竟是什么意思呢?为何要这么作呢?设计
咱们先从二进制提及。咱们都知道,一个字节是由8
个二进制位构成的,最小就是0000 0000
,最大就是1111 1111
。那么一个字节所能表示的最多字符数就是2
的8
次方,也就是256
。对于26
个英文字母来讲,大小写全算上就是52
个,再加上10
个阿拉伯数字,62
个字符,用能够表达256
个不一样字符的一个字节来存储是足够了。3d
可是,咱们中国的经常使用汉字就有3000
多个,用一个只能表达256
个字符的字节显然是不够存储的。至少也须要有2
个字节,1
个字节是8
个二进制位,2
个字节就是16
个二进制位,最多能够表达2
的16
次方,也就是256*256=65536
。65536
个字符足够容纳全部中国的汉字,外带日语、韩语、阿拉伯语、稀其古怪语等等各类各样的字符。因此这样就产生了Unicode
,由于它用2
字节表示字符,因此更严格来说应该叫UCS-2
,后来由于怪字符太多,2
字节都不够用了,因此又搞出来了一个4
字节表示的方法,称做UCS-4
。不过如今对绝大多数人来说UCS-2
已是足够了。code
Unicode
原本是一个好东西,用2
字节表示65536
种字符,全人类皆大欢喜的事情。可是恰恰有一帮子西洋人,非要认为这个东西是一种浪费,说咱们英文就最多只须要26
个字母就够了,1
个字节就够了,为何要浪费2
字节呢?好比说字母A
就是0100 0001
,这一个字节就够了的东西,你弄2
字节,非要在前面加8
个0
,0000 0000 0100 0001
,这不是浪费吗?咱们就偏要用1
字节表示英文。blog
好吧,咱们全人类只好作妥协,规定每一个字节,只要看见0
打头的,就知道这是英文字母,这确定不是汉字,只有看见1
开头的,才认为这是汉字。three
可是咱们汉字用1
个字节表示不下,那好办,用2
个1
开头的字符表示1
个汉字。这样原本16
个二进制位,减去2
个开头的1
,只剩下14
个二进制位了,2
的14
次方就是16384
个字符,对于中文来说,也是足够用了。可是无奈他们仍是想表达65536
种字符,那怎么办呢?就须要3
个字节才能容纳得下了,因而UTF-8
粉墨登场。ip
首先,首位为0
的字符被占了,只要遇到0
开头的字符,就知道这是一个1
字节的字符,没必要再日后数了,直接拿来用就能够,最多表示128
种字符,从0000 0000
到0111 1111
,也就是从0
到127
。get
接下来的事情就比较蹊跷了。咱们怎么用1
开头的字符既表示2
字节,又表示3
字节呢?假设咱们只判断首位的1
,这显然是不行的,没有办法区分,因此咱们能够用10
或者11
开头的字符来表示2
字节,可是3
字节又该以什么开头?或者能够用10
开头表示2
字节,用11
开头表示3
字节?那么4
字节的字符未来又该怎么办?也许咱们能够用110
开头表示3
字节,用111
开头表示4
字节?那么5
字节6
字节呢?彷佛咱们看到了一个规律:前面的1
越多,表明字节数越多。it
这时候,看一下咱们的第一种方案:用10
开头表示2
字节,那么咱们的一个字符将是
10xx xxxx 10xx xxxx
用110
表示3
字节,那么一个3
字节的字符将是:
110x xxxx 110x xxxx 110x xxxx
这样无疑是能区分得开的。可是4
字节怎么办?
1110 xxxx 1110 xxxx 1110 xxxx 1110 xxxx
吗?这样也能区分开,但彷佛有点浪费。由于每一个字节的前半扇都被无用的位占满了,真正有意义的只有后面一半。
或者咱们干脆这样作得了,咱们来设计方案二:为了节省起见,全部后面的字符,咱们通通都以10
开头,只要碰见10
咱们就知道它只是整个字符流的一部分,它确定不是开头,可是10
这个开头已经被咱们刚刚方案一的2
字节字符占用了,怎么办?好办,把2
字节字符的开头从10
改为110
,这样它就确定不会和10
冲突了。因而2
字节字符变成
110x xxxx 10xx xxxx
再日后顺推,3
字节字符变成
1110 xxxx 10xx xxxx 10xx xxxx
4
字节字符变成
1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
好像比刚才的方案一有所节省呢!而且还带来了额外的好处:若是我没有见到前面的110
或者1110
开头的字节,而直接见到了10
开头的字节,毫无疑问地能够确定我遇到的不是一个完整字符的开头,我能够直接忽略这个错误的字节,而直接找下一个正确字符的开头。
这个改良以后的方案二就是UTF-8
!
如今,咱们来算一下在UTF-8
方案里,每一种字节能够表示多少种字符。
1
字节的字符,以0
开头的,0xxx xxxx
,后面7
个有效位,2
的7
次方,最多能够表示128
种字符。
2
字节的字符,110x xxxx 10xx xxxx
,数一数,11
个x
,因此是2
的11
次方,2
的10
次方是1024
,11
次方就是2048
,很不幸,只能表示2048
种字符,而咱们的经常使用汉字就有3000
多个,看来在这一区是放不下了,只好挪到3
字节。
3
字节的字符,1110 xxxx 10xx xxxx 10xx xxxx
,数一数,16
个x
,2
的16
次方,最多能够表示65536
个字符,因此咱们的汉字就放在这一区,因此在UTF-8
方案里咱们的汉字都是以3
个字节表示的。
因此这也就是这一张表的来历:
那么UTF-8
的8
是从哪儿来的呢?它的意思就是说咱们以2
的8
次方为一个字节,为一个最小单元。那么若是咱们以2
的16
次方为一个最小单元,这就变成了UTF-16
,它的规则和UTF-8
相同,惟一不一样的是它最小也要用16
个2
进制位表示一个字符,而16
个2
进制位直接能够表示65536
种字符,因此在UTF-16
方案里,咱们汉字直接就能够如英文同样被冠冕堂皇地放在第1
区了,也就是说,和英文具备同等的身份,都占用16
个2
进制位,也就至关于UTF-8
里的2
字节哦,看,这样一来,若是咱们用UTF-16
来存储英文的话,会形成浪费,由于英文在UTF-8
里只占1
字节,而在UTF-16
里要占2
字节,可是若是咱们用UTF-16
来存储中文的话,不但不浪费,反而还节省了呢!由于咱们的中文在UTF-8
里要占用3
字节,而在UTF-16
里只占用2
字节,节省了33%
之多呢!