目录


Unicode、UTF-8、UTF-16、UTF-32有什么区别?

这是之前在阿里淘系前端面经上看到的一道题,自己也不是很了解,查阅了相关资料,来整理一下。

1. Unicode

在说​​Unicode​​​之前我们需要先了解一下​​ASCII​​码:

ASCII 码(​​American Standard Code for Information Interchange​​)称为美国标准信息交换码。

  • 它是基于拉丁字母的一套电脑编码系统。
  • 它定义了一个用于代表常见字符的字典。
  • 它包含了"A-Z"(包含大小写),数据"0-9" 以及一些常见的符号。
  • 它是专门为英语而设计的,有128个编码,对其他语言无能为力

​ASCII​​​码可以表示的编码有限,要想表示其他语言的编码,还是要使用​​Unicode​​​来表示,可以说​​Unicode​​​是​​ASCII​​ 的超集。

​Unicode​​​全称 ​​Unicode Translation Format​​​,又叫做统一码、万国码、单一码。​​Unicode​​ 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

Unicode的实现的方式:

​Unicode​​的实现方式(也就是编码方式)有很多种,常见的是UTF-8UTF-16UTF-32USC-2

2. UTF-8

​UTF-8​​​是使用最广泛的​​Unicode​​​编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容​​ASCII​​码的128个字符。

注意:​UTF-8​​​ 是一种编码方式,​​Unicode​​是一个字符集合。

​UTF-8​​的编码规则:

  • 对于单字节的符号,字节的第一位为0,后面的7位为这个字符的​​Unicode​​​编码,因此对于英文字母,它的​​Unicode​​​编码和​​ACSII​​编码一样。
  • 对于n字节的符号,第一个字节的前n位都是1,第n+1位设为0,后面字节的前两位一律设为10,剩下的没有提及的二进制位,全部为这个符号的​​Unicode​​码 。

我们来看一下具体的​​Unicode​​​编号范围与对应的​​UTF-8​​二进制格式 :

编码范围(编号对应的十进制数)

二进制格式

0x00—0x7F (0-127)

0xxxxxxx

0x80—0x7FF (128-2047)

110xxxxx 10xxxxxx

0x800—0xFFFF (2048-65535)

1110xxxx 10xxxxxx 10xxxxxx

0x10000—0x10FFFF (65536以上)

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那我们该如何通过具体的​​Unicode​​​编码,进行具体的​​UTF-8​​编码呢?

步骤:

  • 找到该​​Unicode​​编码的所在的编号范围,进而找到与之对应的二进制格式
  • 将​​Unicode​​编码转换为二进制数(去掉最高位的0)
  • 将二进制数从右往左一次填入二进制格式的​​X​​​中,如果有​​X​​未填,就设为0

我们来看一个实际的例子:

” 字的​​Unicode​​​编码是:​​0x9A6C​​​,整数编号是​​39532​

(1)首选确定了该字符在第三个范围内,它的格式是 ​​1110xxxx 10xxxxxx 10xxxxxx​​​ (2)39532对应的二进制数为​​1001 1010 0110 1100​​ (3)将二进制数填入X中,结果是:​​11101001 10101001 10101100​

3. UTF-16

1. 平面的概念

在了解​​UTF-16​​之前,我们先看一下平面的概念:
​​​Unicode​​编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区存放65536(216)个字符,这称为一个平面,目前总共有17 个平面。

最前面的一个平面称为基本平面,它的码点从0 — 216-1,写成16进制就是​​U+0000 — U+FFFF​​,那剩下的16个平面就是辅助平面,码点范围是 ​​U+10000—U+10FFFF​​。

2. UTF-16 概念:

​UTF-16​​​也是​​Unicode​​​编码集的一种编码形式,把​​Unicode​​​字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。​​Unicode​​​字符的码位需要1个或者2个16位长的码元来表示,因此​​UTF-16​​也是用变长字节表示的。

3. UTF-16 编码规则:

  • 编号在​​U+0000—U+FFFF​​ 的字符(常用字符集),直接用两个字节表示。
  • 编号在​​U+10000—U+10FFFF​​ 之间的字符,需要用四个字节表示。

4. 编码识别

那么问题来了,当我们遇到两个字节时,我们怎么知道是把它当做一个字符还是和后面的两个字节一起当做一个字符呢?

​UTF-16​​​ 编码肯定也考虑到了这个问题,在基本平面内,从 ​​U+D800 — U+DFFF​​ 是一个空段,也就是说这个区间的码点不对应任何的字符,因此这些空段就可以用来映射辅助平面的字符。

辅助平面共有 220 个字符位,因此表示这些字符至少需要 20 个二进制位。​​UTF-16​​​ 将这 20 个二进制位分成两半,前 10 位映射在 ​​U+D800 — U+DBFF​​,称为高位(H),后 10 位映射在 ​​U+DC00 — U+DFFF​​,称为低位(L)。这就相当于,将一个辅助平面的字符拆成了两个基本平面的字符来表示。

因此,当我们遇到两个字节时,发现它的码点在 ​​U+D800 —U+DBFF​​​之间,就可以知道,它后面的两个字节的码点应该在 ​​U+DC00 — U+DFFF​​ 之间,这四个字节必须放在一起进行解读。

5. 举例说明

以 “𡠀” 字为例,它的 ​​Unicode​​​ 码点为 ​​0x21800​​,该码点超出了基本平面的范围,因此需要用四个字节来表示,步骤如下:

  • 首先计算超出部分的结果:​​0x21800 - 0x10000​
  • 将上面的计算结果转为20位的二进制数,不足20位就在前面补0,结果为:​​0001000110 0000000000​
  • 将得到的两个10位二进制数分别对应到两个区间中
  • ​U+D800​​​ 对应的二进制数为​​1101100000000000​​​, 将​​0001000110​​​填充在它的后10 个二进制位,得到​​1101100001000110​​​,转成 16 进制数为​​0xD846​​​。同理,低位为​​0xDC00​​​,所以这个字的​​UTF-16​​​ 编码为​​0xD846 0xDC00​

4. UTF-32

​UTF-32​​ 就是字符所对应编号的整数二进制形式,每个字符占四个字节,这个是直接进行转换的。该编码方式占用的储存空间较多,所以使用较少。

比如“” 字的Unicode编号是:​​U+9A6C​​​,整数编号是​​39532​​​,直接转化为二进制:​​1001 1010 0110 1100​​,这就是它的UTF-32编码。

5. 总结

简单的了解完这几个概念,那就来简单回答一下最开始的那个问题:

Unicode、UTF-8、UTF-16、UTF-32有什么区别?

  • ​Unicode​​​ 是编码字符集(字符集),而​​UTF-8​​​、​​UTF-16​​​、​​UTF-32​​是字符集编码(编码规则)
  • ​UTF-16​​​ 使用变长码元序列的编码方式,相较于定长码元序列的​​UTF-32​​​算法更复杂,甚至比同样是变长码元序列的​​UTF-8​​也更为复杂,因为其引入了独特的代理对这样的代理机制
  • ​UTF-8​​​需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而​​UTF-16​​不会判断开头标志,即使错也只会错一个字符,所以容错能力教强
  • 如果字符内容全部英文或英文与其他文字混合,但英文占绝大部分,那么用​​UTF-8​​​就比​​UTF-16​​​节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么​​UTF-16​​就占优势了,可以节省很多空间

其实最主要的还是要理解这几个名词的概念,以及编码的步骤和原理。

最后的最后

看了很多篇网上的文章,也查了很多资料,有些地方觉得比较好的就搬过来了,自己也有总结整理。总之,通过写这篇文章的过程,对​​Unicode​​​、​​UTF-8​​​、​​UTF-16​​​、​​UTF-32​​都有了全新的认识,如果有不准确的地方,欢迎指正鸭~