前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UTF-8 为什么会比 UTF-16 浪费?

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

作者头像
前端教程
发布2018-03-05 13:08:19
9180
发布2018-03-05 13:08:19
举报
文章被收录于专栏:程序员IT圈程序员IT圈

上帝说:『首先取下栓,然后不多不少数到三。应该数到三,你数到的数字是三。你除了数到三,既不要数到四,也不要数到二,五是数多了。「三」一旦被数到,成为被数到的第三个数字,就高高的向敌人扔出安提拉之神圣手榴弹,阿门。』 —— 巨蟒与圣杯 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字节,那么我们的一个字符将是

代码语言:javascript
复制
10xx xxxx 10xx xxxx

110表示 3字节,那么一个 3字节的字符将是:

代码语言:javascript
复制
110x xxxx 110x xxxx 110x xxxx

这样无疑是能区分得开的。但是 4字节怎么办?

代码语言:javascript
复制
1110 xxxx 1110 xxxx 1110 xxxx 1110 xxxx

吗?这样也能区分开,但似乎有点浪费。因为每个字节的前半扇都被无用的位占满了,真正有意义的只有后面一半。

或者我们干脆这样做得了,我们来设计方案二:为了节省起见,所有后面的字符,我们统统都以 10开头,只要遇见 10我们就知道它只是整个字符流的一部分,它肯定不是开头,但是 10这个开头已经被我们刚刚方案一的 2字节字符占用了,怎么办?好办,把 2字节字符的开头从 10改成 110,这样它就肯定不会和 10冲突了。于是 2字节字符变成

代码语言:javascript
复制
110x xxxx 10xx xxxx

再往后顺推, 3字节字符变成

代码语言:javascript
复制
1110x xxxx 10xx xxxx 10xx xxxx

4字节字符变成

代码语言:javascript
复制
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%之多呢!

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员IT圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • UTF-8的来历
  • UTF-8表示的字符数
  • UTF-8和UTF-16
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档