Unicode 编码
Unicode,官方中文名称为统一码,也译名为万国码、国际码、单一码,是计算机科学领域的业界标准。它整理、编码了世界上大部分的文字系统,使得电脑可以用更为简单的方式来呈现和处理文字。
那么简单来说,一切能看到的字符,比如:英文,中文,日文,韩文,德文,少数民族字符,特殊符号,甚至 emoji 等等,一切用来展示的字符的在 Unicode 中都包含在内,其实 Unicode 就是一种字符集,每个字符都有它自己对应的编码位置,通俗叫 码点 或 码位。
Unicode 它码点规定了符号的二进制代码,为了方便表示,在开头以 U+ 开头,然后紧接十六进制 ,譬如:U+0061,U+0039,U+005d 它们分别是是 a,9, ]
直接已经收录超过 13 万个字符,但是这么多符号,Unicode 不是一次性定义的,而是分区定义。
每个区可以存放 65536 个(2^16)字符,称为一个平面(plane),其中最重要的平面就是基本多文种平面(U+0000-U+FFFF),简称 BMP ,计算机中常见的大多数字符都存在这个平面当中.
计算机如何存储 string
string 里需要表达 Unicode 中的字符(比如英文,中文,特殊符号等等)的前提是需要知道字符对应的码点位置。
问题来了,难道计算机是直接把码点的二进制数据直接存起来的?
最小存储的单位是比特(Bit),代表一位二进制
0或1,当有8个 Bit 的时候 譬如:00001001
就是常说的1字节(Byte) = 8位二进制 = 2位的十六进制
那么根据简单的推算,一个字节的构成有 2^8 就是 256 种情况,那么一个 ASCII 字符能表示 Unicode 字符集区间是 U+0000-U+00FF, 这个区间代表一个字节,那如果我想写出大于 U+00FF 后面的字符的话,比如中文 U+4E00-U+9FA5,就是起码两个字节去表示?
感觉好像也就是那么一回事,但是真的有这么简单? 细想一下,计算机那么笨,怎么知道遇到 2 个字节的字符表示的是一个字符,而不是分别表示两个字符呢?
所以这中间还需要进行一道码点的编码转换的过程,计算机最终存储的是该字符编码后的二进制数据,如何编码就涉及不同的编码方案。
所以早期的编码方案就有一种设想,所有字符统一用4个字节表示,比如 一个英文字母 a 它最终可能需要4个字节去存储,,多余的位数就用0去补,譬如 0x00000061,尽管英文对应的码点是非常靠前的,但仍需要4个字节去存储的话就非常浪费空间了。
这种方案就是UTF-32,因为占用的空间太大,以至于不太现实而没有采纳。
那么在 string 到底是以 Unicode 什么编码形式存储的呢?(以javascript 为例)
Javascript 的 string 采取什么编码方案?
目前 Unicode 的编码方案分别有
- UTF-8
- UTF-16
- UTF-32
在 ecma 262 中的第10页中,介绍了 String 的编码规则是以 UTF-16 的形式存储。
UTF-16
历史
UTF-16编码源于UCS-2,是Unicode最早的编码方式。
UCS-2编码仅覆盖了基本平面(即BMP,第0平面)中的码点,使用固定的两字节将字符编号(类似于Unicode中的码点值)直接映射为字符编码,中间未经过任何的编码算法转换。
很明显,16位的二进制位(范围为0x0000 ~ 0xFFFF)无法表示Unicdoe引入的增补平面中的码点(平面1 ~ 16,码点范围为0x10000~0x10FFFF),为此,Unicode在UTF-16编码中使用“代理(代替)机制”来解决这个问题,代理机制使用4个字节来表示增补平面中的码点,从而使UTF-16成为一种变长编码方式。
因此,若软件仅支持UCS-2编码,则意味着仅支持UCS字符集或Unicode字符集基本平面中的字符,而不支持增补平面中的字符。
UTF-16编码简介
UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点
-
基本平面(BMP)(U+0000-U+FFFF) 范围内大部分字符都以固定长度2个字节存储**。
-
辅助平面(BMP 以外)(U+10000-U+10FFFF)的字符占用 4 个字节存储。有趣的是在基本平面内,从 (U+D800-U+DFFF)的范围内是一个空段,即这些码点不对应任何字符,在这个区间,划分了两个区间,其中4个字节里前2个字节是分配在 U+D800到U+DBFF 内,后2个字节是分配剩余的区间里(U+DC00 到 U+DFFF) ,前者称高位(H) ,后者称低位(L) ,辅助平面就是利用了这个区间来映射字符。
代理(代替)机制与UTF-16编码规则
1、代理机制的基本思想
代理可简单理解为代替,其基本思想就是使用两个基本平面中未定义(或未使用)的码点合起来代替一个增补平面的码点。
2、代理机制的基本规则
基本平面中的字符(除U+D800~U+DFFF外)仍然使用固字两字节直接映射的方式编码,即,基本平面字符的字符编号(码点值)就是字符编码。
增补平面的字符使用“代理机制”这一编码算法进行编码,使用代理机制后需要使用4字节来表示Unicode增补平面中的码点,因此,UTF-16也是一种变长编码方式。
3、代理对和代理区
代理对是指基本平面中用来代替增补平面码点的两个码点,也就是说,代理对是一对(即,两个)码点值。
代理区
基本平面中用来代替增补平面码点的未使用的码点区域被称为代理区,其码点范围为0xD800 ~ 0xDFFF,共2048个码点,
代理区又分为高代理码点(或称为高代理码元,范围为0xD800 ~ 0xDBFF)和低代理码点(或称为低代理码元,范围为0xDC00 ~ 0xDFFF),图3为代理区的分类。
需要注意的是,代理区中的码点都未定义字符,这些码点主要用于代理增补平面中的码点。假设为代理区中的码点指定了字符,那么一个代理区中的码元,到底是表示基本平面的字符呢还是增补平面中的字符的代理码元呢?这就产生了冲突,所以,为避免冲突,代理区中的码点都未定义字符。
4、UTF-16编码规则
把高代理码点和低代理码点合起来组成“代理对”,使用代理对来代替增补平面的码点,代理对刚好可完全表示增补平面内的所有码点(原因见后文)。比如,增补平面的第一个码点0x10000的UTF-16编码是0xD800 DC00,同理,第二个码点0x10001的UTF-16编码是0xD800 DC01,其余以此类推。
三.UTF-16编码方法
1、方法1:查表
根据UTF-16的编码规则可制出如表6所示的一个表格,然后通过查表的方式查找增补字符的编码,但这种方法非常麻烦,实际编码时并不使用。
2、方法2:计算法
①、将增补字符的码点值减去0x10000,得到一个20位长的二进制数
②、将得到的20位长二进制数拆分为高10位比特和低10位比特
③、20位长的高10位比特加上0xD800得到第一个代理码点,即高代理码点
④、20位长的低10位比特加上0xDC00得到第二个代理码点,即低代理码点
⑤、将得到的高代理码点和低代理码点组合成“代理对”,便得到了增补字符的UTF-16编码
⑥、示例:求增补平面码点值为U+10437的UTF-16编码
将0x10437减去0x10000,得到0x00437,二进制为0000 0000 0100 0011 0111
将高10位,即0000 0000 01加上0xD800(二进制为1101 1000 0000 0000),得到高代理码点为:0xD801(二进制为1101 1000 0000 0001)
将低10位,即00 0011 0111加上0xDC00(二进制为1101 1100 0000 0000),得到低代理码点为:0xDC37(二进制为1101 1100 0011 0111)
将高代理码点和低代理码点组合成代理对得到UTF-16编码为0xD801 DC37
3、方法3:二进制位填补法
①、原理
高代理码点的起始值0xD800(二进制为1101 1000 0000 0000)和低代理码点的起始值0xDC00(二进制为1101 1100 0000 0000),可发现,他们的前6位分别为110110和110111,而后10位都是0,总共有20位(220=1048576),刚好可表示增补平面中的所有码点(0x10000 ~ 0x10FFFF,共220=1048576个码点),若利用代理码点的这20位来表示增补码点,则高代理码点的范围为1101 1000 0000 0000(0xD800) ~ 1101 1011 1111 1111(0xDBFF),低代理码点的范围为1101 1100 0000 0000(0xDC00) ~ 1101 1111 1111 1111(0xDFFF),刚好在高代理码点和低代理码点的范围内。因此,可利用填补二进制位的方法来编码UTF-16
②、步骤1:
将高代理码点和低代理码点分别展开为如图4所示的形式,其中高代理单元的110110和低代理单元的110111是固定不变的数(定数),p和x是变数,去掉定数后组合起来就是pppp xxxx xxxx xxxx xxxx,共20位,刚好可表示全部增补码点,其中pppp表示16个增补平面的编码(24=16),紧接着的16个x表示某个增补平面内的码点。
③、步骤2、
将增补字符的码点值减去0x10000,得到一个20位长的二进制数
④、步骤3、
将得到的20位长二进制数依次填补步骤1中的变数便得到UTF-16的编码
⑤、示例:
求增补平面码点值为U+10437的UTF-16编码
将0x10437减去0x10000,得到0x00437,二进制为0000 0000 0100 0011 0111 将0000 0000 0100 0011 0111依次填补高代理单元和低代理单元中的变数(如图5所示),得到其编码为0xD801 DC37
参考或者说直接复制自如下两个链接:
链接1:
zhuanlan.zhihu.com/p/386511092…
链接2:
————————————————
版权声明:本文为CSDN博主「hyongilfmmm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/hyongilfmmm…