UTF-8 为什么会比 UTF-16 浪费?

上帝说:『首先取下栓,然后不多不少数到三。应该数到三,你数到的数字是三。你除了数到三,既不要数到四,也不要数到二,五是数多了。「三」一旦被数到,成为被数到的第三个数字,就高高的向敌人扔出安提拉之神圣手榴弹,阿门。』 —— 巨蟒与圣杯 Monty Python and the Holy Grail (1975)

UTF-8的来历

UTF-8的规范里充斥着这样神秘的句子:“第一个位元组由 110开始,接着的位元组由 10开始”,“第一个位元组由 1110开始,接着的位元组由 10开始”。

那么这到底是什么意思呢?为什么要这么做呢?

我们先从二进制说起。我们都知道,一个字节是由 8个二进制位构成的,最小就是 00000000,最大就是 11111111。那么一个字节所能表示的最多字符数就是 28次方,也就是 256。对于 26个英文字母来说,大小写全算上就是 52个,再加上 10个阿拉伯数字, 62个字符,用可以表达 256个不同字符的一个字节来存储是足够了。

但是,我们中国的常用汉字就有 3000多个,用一个只能表达 256个字符的字节显然是不够存储的。至少也需要有 2个字节, 1个字节是 8个二进制位, 2个字节就是 16个二进制位,最多可以表达 216次方,也就是 256*256=6553665536个字符足够容纳所有中国的汉字,外带日语、韩语、阿拉伯语、稀其古怪语等等各种各样的字符。所以这样就产生了 Unicode,因为它用 2字节表示字符,所以更严格来讲应该叫 UCS-2,后来因为怪字符太多, 2字节都不够用了,所以又搞出来了一个 4字节表示的方法,称作 UCS-4。不过现在对绝大多数人来讲 UCS-2已经是足够了。

Unicode本来是一个好东西,用 2字节表示 65536种字符,全人类皆大欢喜的事情。但是偏偏有一帮子西洋人,非要认为这个东西是一种浪费,说我们英文就最多只需要 26个字母就够了, 1个字节就够了,为什么要浪费 2字节呢?比如说字母 A就是 01000001,这一个字节就够了的东西,你弄 2字节,非要在前面加 800000000001000001,这不是浪费吗?我们就偏要用 1字节表示英文。

好吧,我们全人类只好做妥协,规定每个字节,只要看见 0打头的,就知道这是英文字母,这肯定不是汉字,只有看见 1开头的,才认为这是汉字。

但是我们汉字用 1个字节表示不下,那好办,用 21开头的字符表示 1个汉字。这样本来 16个二进制位,减去 2个开头的 1,只剩下 14个二进制位了, 214次方就是 16384个字符,对于中文来讲,也是足够用了。但是无奈他们还是想表达 65536种字符,那怎么办呢?就需要 3个字节才能容纳得下了,于是 UTF-8粉墨登场。

首先,首位为 0的字符被占了,只要遇到 0开头的字符,就知道这是一个 1字节的字符,不必再往后数了,直接拿来用就可以,最多表示 128种字符,从 0000000001111111,也就是从 0127

接下来的事情就比较蹊跷了。我们怎么用 1开头的字符既表示 2字节,又表示 3字节呢?假设我们只判断首位的 1,这显然是不行的,没有办法区分,所以我们可以用 10或者 11开头的字符来表示 2字节,但是 3字节又该以什么开头?或者可以用 10开头表示 2字节,用 11开头表示 3字节?那么 4字节的字符将来又该怎么办?也许我们可以用 110开头表示 3字节,用 111开头表示 4字节?那么 5字节 6字节呢?似乎我们看到了一个规律:前面的 1越多,代表字节数越多。

这时候,看一下我们的第一种方案:用 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字节字符变成

1110x xxxx 10xx xxxx 10xx xxxx

4字节字符变成

1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx

好像比刚才的方案一有所节省呢!并且还带来了额外的好处:如果我没有见到前面的 110或者 1110开头的字节,而直接见到了 10开头的字节,毫无疑问地可以肯定我遇到的不是一个完整字符的开头,我可以直接忽略这个错误的字节,而直接找下一个正确字符的开头。

这个改良之后的方案二就是 UTF-8

UTF-8表示的字符数

现在,我们来算一下在 UTF-8方案里,每一种字节可以表示多少种字符。

1字节的字符,以 0开头的, 0xxxxxxx,后面 7个有效位, 27次方,最多可以表示 128种字符。

2字节的字符, 110xxxxx10xxxxxx,数一数, 11x,所以是 211次方, 210次方是 102411次方就是 2048,很不幸,只能表示 2048种字符,而我们的常用汉字就有 3000多个,看来在这一区是放不下了,只好挪到 3字节。

3字节的字符, 1110xxxx10xxxxxx10xxxxxx,数一数, 16x216次方,最多可以表示 65536个字符,所以我们的汉字就放在这一区,所以在 UTF-8方案里我们的汉字都是以 3个字节表示的。

所以这也就是这一张表的来历:

UTF-8和UTF-16

那么 UTF-88是从哪儿来的呢?它的意思就是说我们以 28次方为一个字节,为一个最小单元。那么如果我们以 216次方为一个最小单元,这就变成了 UTF-16,它的规则和 UTF-8相同,唯一不同的是它最小也要用 162进制位表示一个字符,而 162进制位直接可以表示 65536种字符,所以在 UTF-16方案里,我们汉字直接就可以如英文一样被堂而皇之地放在第 1区了,也就是说,和英文具有同等的身份,都占用 162进制位,也就相当于 UTF-8里的 2字节哦,看,这样一来,如果我们用 UTF-16来存储英文的话,会造成浪费,因为英文在 UTF-8里只占 1字节,而在 UTF-16里要占 2字节,但是如果我们用 UTF-16来存储中文的话,不但不浪费,反而还节省了呢!因为我们的中文在 UTF-8里要占用 3字节,而在 UTF-16里只占用 2字节,节省了 33%之多呢!

觉得本文对你有帮助?请分享给更多人。

原文发布于微信公众号 - 程序员宝库(chengxuyuanbaoku)

原文发表时间:2018-01-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏老马说编程

计算机程序的思维逻辑 (3) - 基本运算

运算 上节我们介绍了给数据赋值,有了初始值之后,可以对数据进行运算。计算机之所以称为"计算"机,是因为发明它的主要目的就是运算。运算有不同的类型,不同的数据类型...

1909
来自专栏落影的专栏

程序员进阶之算法练习(三十四)LeetCode专场

LeetCode上的题目是大公司面试常见的算法题,今天的目标是拿下5道算法题: 1、2、3题都是Medium的难度,大概是头条的面试题水准; 4、5题是Hard...

1293
来自专栏恰童鞋骚年

剑指Offer面试题:9.二进制中1的个数

  一个基本的思路:先判断整数二进制表示中最右边一位是不是1。接着把输入的整数右移一位,此时原来处于从右边数起的第二位被移到最右边了,再判断是不是1。这样每次移...

752
来自专栏python读书笔记

python 数据分析基础 day9-datetime类型常用对象以及函数日期类型的运算

今天是读《python数据分析基础》的第9天,今天将通过python的date模块来总结日期类型。 常用对象以及函数 对象 可通过date模块创建创建以下对象:...

3126
来自专栏程序员叨叨叨

8.1 函数第 8 章 函数与程序设计

通过第 5 章到第 7 章的阅读,我们已经知道了怎么声明变量(第 5 章),怎么写表达式和语句(第 6 章),怎么将输入 \ 输出参数绑定到语义词(第 7 章)...

1012
来自专栏Java帮帮-微信公众号-技术文章全总结

第十一天 面向对象-接口多态【悟空教程】

1594
来自专栏醒者呆

面向程序员编程——精研排序算法

这篇文章很长,我花了好久的时间(中间公司出了bug,加班了好几天( ¯ ¨̯ ¯̥̥ ))进行整理,如有任何疑问,欢迎随时留言。 关键字:排序算法,时间...

3855
来自专栏Java编程

四道Java基础题,你能对几道?

如果这道题你能得出正确答案,并能了解其中的原理的话。说明你基础还可以。如果你的答案 是 true 和true的话,你的基础就有所欠缺了。

1.1K1
来自专栏云霄雨霁

Java--集合类之Vector、BitSet、Stack、Hashtable

1777
来自专栏禁心尽力

Java Collection知识总结

首先说说java中常用的集合容器:ArrayList,LinkedList,Vector,HashMap,Hashtable,HashSet,TreeSet。【...

19910

扫码关注云+社区

领取腾讯云代金券