本文将介绍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 个字符编码。
其中每个平面的作用都不一样:
基本平面 | 辅助平面 | 辅助平面 | 辅助平面 | 辅助平面 | 辅助平面 | 辅助平面 | |
---|---|---|---|---|---|---|---|
平面编号 | Plane 0 | Plane 1 | Plane 2 | Plane 3 | Plane 4-13 | Plane 14 | Plane 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) | 保留平面 | 特殊用途 | 个人使用 |
简写名称 | BMP | SMP | SIP | TIP | SSP | SPUA-A/B |
我们日常接触到的各种语言类的字符基本都在0号平面(Plane 0),也就是基本多语言平面,大部分emoji表情都在1号平面内(Plane 1),最后两个平面是私有编码空间(PUA),不会分配字符编码,专门给软件自定义用的。
0号平面(Plane 0),也就是基本多语言平面,它的各种字符的分布:
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) | 1 | 2 |
8 到 11 位 (U+0080 - U+07FF) | 2 | 2 |
12 到 16 位 (U+0800 - U+FFFF) | 3 | 2 |
17 到 21 位 (U+10000 - U+10FFFF) | 4 | 4 |
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码。
文章评论