1. 首页
  2. 基础
  3. 正文

Unicode、UTF-8、UTF-16、UCS-2区别

2023年10月17日 648点热度 0人点赞 0条评论

本文将介绍Unicode、UTF-8、UTF-16、UCS-2区别和转换规则。

什么是Unicode?

Unicode是字符编码集合,每个字符编码都对应着唯一的字符。这里面就包含了几乎世界上所有的字符,例如大小写字母、数字、希腊字母、汉字、特殊符号、甚至是emoji表情等等(具体编码所对应的字符可以搜索Unicode字符列表)。

在表示一个Unicode的字符时,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。

比如小写字母“h”,Unicode编码为U+68;简体中文字符“梦”,Unicode编码为U+68A6。

最开始,Unicode 的编码范围是 U+0000 ~ U+FFFF,后来发现根本不够用,所以又将编码范围扩大到了 U+000000 ~ U+10FFFF,可用来表示一百多万的字符。

这一百多万的编码被划分成 17 个平面(planes),编号从0~16,每个平面可容纳 2^16 即 65,536 个字符编码。

Unicode的17个平面

其中每个平面的作用都不一样:

基本平面辅助平面辅助平面辅助平面辅助平面辅助平面辅助平面
平面编号Plane 0Plane 1Plane 2Plane 3Plane 4-13Plane 14Plane 15-16
范围U+ 0000
U+ FFFF
U+ 10000
U+ 1FFFF
U+ 20000
U+ 2FFFF
U+ 30000
U+ 3FFFF
U+ 40000
U+ DFFFF
U+ E0000
U+ EFFFF
U+ F0000
U+ 10FFFF
名称基本多语言平面(BasicMultilingual
Plane)
补充多语言平面(Supplementary
MultilingualPlane)
补充表意字符平面(Supplementary
IdeographicPlane)
第三表意文字平面
(Tertiary
Ideographic
Plane)
保留平面特殊用途个人使用
简写名称BMPSMPSIPTIPSSPSPUA-A/B

我们日常接触到的各种语言类的字符基本都在0号平面(Plane 0),也就是基本多语言平面,大部分emoji表情都在1号平面内(Plane 1),最后两个平面是私有编码空间(PUA),不会分配字符编码,专门给软件自定义用的。

0号平面(Plane 0),也就是基本多语言平面,它的各种字符的分布:

Unicode基本多语言平面各种字符的分布

UTF-8

当我需要将100个小写字母“h”,和100个简体中文字符"梦"进行存储和传输时,会出现一个这样的问题:

小写字母“h”的Unicode编码为U+68,转为二进制:0110 1000,八个位一个字节。
中文字符“梦”的Unicode编码为U+68A6,转为二进制:0110 1000 1010 0110,十六个位两个字节。

"h"占用一个字节,“梦”占用两个字节,存储后再读取如何识别这些字节?读取到某个字节时,那这个字节是单独一个"h"字符还是"梦"这个字符的一半?那可能有人会说那就统一使用两个字节进行存储传输,这确实是一个办法,但是当我们只需要存储100个"h"字符时,却需要200个字节空间来存储,这造成了浪费。

而UTF-8就是基于Unicode编码来实现更高效的传输、存储。它们是一种变长的编码,可以使用1~4个字节来表示一个字符,根据Unicode编码所对应的不同字符而变化字节长度,从而降低字符存储空间和提高传输效率。

Unicode编码转UTF-8编码规则

Unicode编码在 U+0000 到 U+007F 范围内的字符

这个范围内的字符用UTF-8编码来表示仅需一个字节就可以:0xxxxxxx。该字节的第一位固定设为0,用于识别这是个单字节字符,后面7位为该字符 Unicode编码的二进制形式的前七位。而且 U+0000 到 U+007F (十进制0到127)范围内的Unicode编码与ASCII编码一致,所以UTF-8编码兼容ASCII编码( 前128个ASCII码用一个字节表示时,二进制0111 1111为最大值,未使用最高位 )。

例如小写字母“a”:
  ASCII编码:二进制为 0110 0001,十六进制为0x61,十进制为97。
  Unicode编码:二进制为 0110 0001,十六进制为U+61,十进制为97(与ASCII编码一致)。
  UTF-8编码:二进制为 0110 0001,十六进制为0x61,十进制为97(二进制取Unicode编码的前七位,最高位固定为0)

Unicode编码在 U+0080 到 U+07FF 范围内的字符

对于这些范围内的字符,显然使用一个字符是无法表示的。这些范围内的UTF-8编码需要占用两个字节,每个字节都以固定的位作为标识开头,形如:110xxxxx 10xxxxxx,第一个字节的前两位为1,后面一位固定为0,说明这是一个两个字节字符;而第二个字节则固定以10开头,说明这是多字节的字符中的一部分。除去这些固定的位以外,剩余十一个位则用来表示Unicode编码的二进制形式前十一个位。

例如国际音标字符“ə”:
  Unicode编码:二进制为 0000 0010 0101 1001 ,十六进制为U+0259
  取上面二进制前十一位 010 0101 1001 填充 110x xxxx 10xx xxxx
  则最终UTF-8编码:二进制为 1100 1001 1001 1001,十六进制为0xC999

Unicode编码在 U+0800 到 U+FFFF 范围内的字符

这些范围内的字符使用UTF-8编码规则表示需要三个字节,形如:1110xxxx 10xxxxxx 10xxxxxx。第一个字节的前三位为1,第四位固定为0,表示这是占用三个字节的字符,第二个字节、第三个字节都固定以10开头。除去这些固定的位以外,剩余16个位则用来表示Unicode编码的二进制形式。

例如中文字符“梦”:
  Unicode编码:二进制为 0110 1000 1010 0110 ,十六进制为U+68A6
  取上面二进制 0110 1000 1010 0110 填充 1110 xxxx 10xx xxxx 10xx xxxx
  UTF-8编码:二进制为 1110 0110 1010 0010 1010 0110,十六进制为0xE6A2A6

Unicode编码在 U+010000 到 U+10FFFF 范围内的字符

这个范围内需要用四个字节来表示,形如:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx,具体规则与上述过程类似。

总结UTF-8编码的二进制规律

一个字节:0xxxxxxx
两个字节:110xxxxx 10xxxxxx
三个字节:1110xxxx 10xxxxxx 10xxxxxx
四个字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

有了以上编码规则,对于解读识别 UTF-8 编码的二进制就非常简单。

如果一个字节的第一位是0,则这个字节单独就是一个字符;如果前两位是1,表示这是占用两个字节的字符的第一个字节,前三位是1就是三个字节,前四个位是1就是四个字节;如果是10开头,那就表示这个字节是多字节字符中一部分。

UTF-16

UTF-16使用2个字节或四个字节来实现Unicode编码。

在Unicode的编码范围U+0000到U+10FFFF中,可以划分为17个平面。第一个平面称为基本多语言平面,从U+0000到到U+FFFF,其他剩余的平面称为辅助平面。在第一个平面的U+D800到U+DFFF范围内是永久保留段,不映射任何字符。UTF-16就利用保留下来的0xD800-0xDFFF区块,来对辅助平面的字符进行编码。

Unicode编码转UTF-16编码规则

从U+0000至U+D7FF以及从U+E000至U+FFFF的这两个范围,UTF-16直接使用二进制的Unicode的编码,占用两个字节。

例如中文字符“梦”:
  Unicode编码:二进制为 0110 1000 1010 0110 ,十六进制为U+68A6
  UTF-16编码相同:二进制为 0110 1000 1010 0110,十六进制为0x68A6

而U+10000到U+10FFFF的范围,占用四个字节。但可以利用U+D800到U+DFFF这个范围,该范围位于保留平面,不映射具体Unicode字符,以此来实现UTF-16。

首先将Unicode编码减去 0x10000,得到的值的范围为 0 到 0xFFFFF,其中最大值0xFFFFF的二进制占用20个位。

把 0x0 到 0xFFFFF这个范围都用二进制的20个位来表示,其中将前10位(高位)值(值的范围为 0 到 0x3FF)加上 0xD800 后得到第一个值,该值的范围是 0xD800 到 0xDBFF。这个值称为前导代理(lead surrogates)。

将后十位(低位)值(值的范围也是 0 到 0x3FF)加上 0xDC00 得到第二个值,这个值的范围是 0xDC00 到 0xDFFF。该值称为后尾代理(trail surrogates)。

最后再将该值合并则为Unicode的UTF-16的编码实现。

以中文字符“𤭢”为例:
  Unicode编码:二进制为 0010 0100 1011 0110 0010 ,十六进制为U+24B62

  将其减去 0x10000
  得到结果:二进制为 0001 0100 1011 0110 0010 ,十六进制为U+14B62

  将其二进制分割成前10位值和后10位值
  分别是 00 0101 0010 与 11 0110 0010

  将前10位加上 0xD800(二进制为1101 1000 0000 0000)
  00 0101 0010 + 1101 1000 0000 0000 = 1101 1000 0101 0010
  结果是 1101 1000 0101 0010

  将后10位加上 0xDC00(二进制为1101 1100 0000 0000)
  11 0110 0010 + 1101 1100 0000 0000 = 1101 1111 0110 0010
  结果是 1101 1111 0110 0010

  最后合并两个值得到
  UTF-16编码的二进制:1101 1000 0101 0010 1101 1111 0110 0010
  UTF-16编码的十六进制:0xD852 DF62

UTF-8与UTF-16的对比

Unicode编码范围UTF-8 编码长度UTF-16 编码长度
7 位以内 (U+0000 - U+007F)12
8 到 11 位 (U+0080 - U+07FF)22
12 到 16 位 (U+0800 - U+FFFF)32
17 到 21 位 (U+10000 - U+10FFFF)44
UTF-8与UTF-16的对比

UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2个字节)存储,但在 U+0000 到 U+007F 范围内的字符比UTF-8要多占用一个字节。正因如此,ASCII编码的字符是用一个字符存储,所以UTF-16无法兼容于ASCII编码。

常用汉字的Unicode编码范围在U+4E00~U+9FFF,在需要大量的中文汉字存储或传输情况下,使用UTF-16编码的体积可能更小。

UTF-16字节顺序标记(BOM)

UTF-16 编码有大尾序(Big endian)和小尾序(Little endian)之别(也叫大小端),即 UTF-16BE 和 UTF-16LE。

比如说“梦”这个字,他的UTF-16的编码十六进制表示为0x68A6,占用两个字节 68 A6。如果68在前,A6在后,这是UTF-16BE(Big endian)大尾序模式。如果A6在前,68在后,这是UTF-16LE(Little endian)小尾序模式。

Byte Order Mark(BOM),即字节顺序标记。 用于标记UTF-16编码的文本文件是 大尾序 还是小尾序。该标记置于文本文件的开头两个字节上,0xfeff 代表 UTF-16BE编码,0xfffe 代表UTF-16LE编码。也就是说当有一个文本文件时,开头两字节的十六进制为 fe ff,那就代表该文件是 UTF-16BE 编码,如果开头是 ff fe, 那就代表该文件是 UTF-16LE 编码 。

以一般来说,以macOS制作或存储的文本使用大尾序格式,以Windows或Linux制作或存储的文本使用小尾序格式。

UCS-2以及与UTF-16的区别

UCS-2定义了一种16位的编码形式,其编码固定占用2个字节,它包含65536个编码空间。

UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。

标签: unicode 字符编码
最后更新:2023年10月22日

Evans Ann

It's no use crying over spilt milk.

点赞

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

归档

  • 2023 年 11 月
  • 2023 年 10 月
  • 2021 年 1 月
  • 2020 年 8 月
  • 2020 年 1 月
  • 2019 年 8 月

分类目录

  • Docker
  • JavaScript
  • Kubernetes
  • Linux
  • PHP
  • Windows
  • 基础
  • 正则表达式
  • 英语

COPYRIGHT © 2018-2024 秋雨沥沥. ALL RIGHTS RESERVED.

赣ICP备18001671号-3