[十]基础数据类型之Unicode编码简介

时间:2022-05-31 20:49:03

编码含义

关于编码的含义,之前也说过,计算机只能存储二进制序列
所以对于字符,保存的时候,需要进行编码为二进制,进行存储
呈现的时候,需要将二进制进行解码,转换成字符的形式
 
有很多种编码方式,比如ASCII
(American Standard Code for Information Interchange,美国信息交换标准代码)
使用一个字节进行编码,一个字节可以表示的最大值为255
很显然,对于英语和其他一些西欧语言来说,足够了,英文字母总共才几个对吧
 
那么对于汉字呢?ASCII显然是不够用的,所以出现了GBK等以支持汉字
那么,日语 韩语呢?他们当然也搞出来一些支持他们文字的编码

Unicode诞生

于是,人们意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,出于这个目的,Unicode诞生了
Unicode 是一种字符集,规定了符号对应的二进制代码
至于这个二进制代码如何存储则没有任何规定,也就是说它是一种编码规定
是编码字符集,而不是实际的编码方案
 
最初Unicode使用一个16位长度的二进制序列,也就是最多支持   (2的16次方-1) 65536个字符,也就是0  ~ 65535
范围是 0x0000 ~0xFFFF 
Unicode使用U+前缀, 加上编码的值,来表示Unicode中的字符编码
也就是  U+0000   ~  U+FFFF 

Unicode不够用了

显然,随着更多字符的增加,  65536 是不够用的
于是Unicode 不得不进行扩展,于是使用8位用作扩展位,形式如下
[十]基础数据类型之Unicode编码简介
一个字节8位可以表示 2的8次方-1 = 256 个数,最大可以扩展为 256 *65526=16777216 个
不过也不需要那么多的字符, 仅仅使用了前面的17个
也就是
00 01 02 03 04 05 06 07 08 0A 0B 0C 0D 0E 0F 10
 
[十]基础数据类型之Unicode编码简介
于是按照扩展位,划分了17个维度,这每个维度,叫做一个平面
17个平面,编号从 0~16
每个平面 65536个字符
17个平面,扩展后总共可以表示1114112个字符
扩展后的范围为
U+000000   ~  U+10FFFF  
 
其中对于第一个U+00  也就是U+0000到U+FFFF ,包含了最常见的字符 
被称作 基本多语言平面  Basic Multilingual Plane, BMP
其余的U+010000   ~  U+10FFFF   被称作辅助平面
至此你应该了解到了,什么是Unicode的
他就是一个具有17个平面,每个平面可以容纳65536个字符的一张巨大的字符码表
一个字符对应一个二进制序列
基本平面中使用四位十六进制数 在零号平面以外的字符则需要使用五位或六位十六进制数了

编码方式

Unicode 没有规定字符对应的二进制码在计算机中如何存储,只是规定了他的值是多少而已
一个字符对应的实际的值,我们称之为代码点 code point
那么一个码点实际的值怎么存储呢?
可能需要1个,可能需要2个,甚至可能需要3个或者4个字节来表示
对于计算机来说,面对着一堆字节,他们知道到底哪个或者哪几个是一个字符呢?
听起来可能有点迷惑,不是知道具体的值了么?怎么还不知道如何表示?

比如数字1 他的码点是1
假如我用两个字节来存储,每个字节的前两位我当做其他的标志位, 设置为11
那么可能结果是这样子的11000000 11000001
显然,他的值并不是1
编码方式只是可以保证,你的字符是按照指定的字符集进行编码的
也就是说如果你告诉我拿出来码点为1 的,我会把1100 0000 1100 0001 解析成数字1
但是并不能保证我保存的数据就是他的码点的真值,0000 0001 ,中间形式是编码方式说了算的

最直观的例子就是网络中报文的传输,都会附加自己的头信息
所以中间传输的数据并不是跟你发送的数据一模一样,中间的数据就是编码形式的存储
但是,接收端接受解析后,就是跟你发送的数据一样的,这就好像是你的字符

 
 
存储的问题就是编码方式的问题,就是表示成什么形式的问题
编码方式有UTF-8 UTF-16  UTF-32
UTF-8 是变长
UTF-32 是定长
UTF-16介于他们之间 2个字节或者4个字节
 

utf-16

UTF-16编码以16位无符号整数为单位
我们把Unicode编码记作U  编码规则如下
如果U<0x010000, 也就是0x000000 ~ 0x00FFFF
U的UTF-16编码,  就是U对应的16位无符号整数
如果U≥0x010000    也就是0x010000 ~ 0x10FFFF
我们先计算下 U'=U-0x010000 
可以得出来 U'  范围是 0x000000 ~ 0x0FFFFF
显然,  U'的最大值为0xFFFFF   也就是最多20个1    也就是可以被写成20个二进制位
既然是20个二进制位,那么我们是不是可以把它拆分成两组呢?
每组10个二进制位    00  0000  0000
它能表示的范围是2的10次方=1024个

BMP是2个字节,16位, 很显然,如果把U' 拆分成两组,每组10个二进制位的话
每一个都能够保存到2个字节内

所以Unicode标准规定:基本多语言平面内,U+D800..U+DFFF的值不对应于任何字符,为代理区 ,其中又分为高代理区和低代理区

U+D800 加上10个二进制位的数值的最大值,可以得到高代理区的范围
U+D800 --->1101 10 00  0000  0000  + 0000 00 11  1111 1111 = 1101 1011 1111 1111 = 0xDBFF

下一个就是0xDBFF +1 = 0xDC00,所以低代理区从0xDC00  开始
0xDC00 加上10个二进制位的数值的最大值,可以得到低代理区的范围
0xDC00----> 1101  1100  0000  0000 + 0000 00 11  1111 1111 = 1101111111111111 = 0xDFFF

高代理区范围  U+D800 ~0xDBFF
低代理区范围   0xDC00 ~ 0xDFFF

代理区间是U+D800....U+DFFF

所以UTF-16的编码方式就是
先计算 U'=U-0x010000
然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx
然后分别计算高位代理和低位代理
U+D800 --->1101 10 00  0000  0000 +   0000 00 yy  yyyy yyyy = 1101 10 yy  yyyy  yyyy

0xDC00----> 1101  1100  0000  0000 + 0000 00 xx  xxxx  xxxx =  1101  11xx  xxxx  xxxx

再精简下步骤
1. 先计算 U'=U-0x010000
2. 然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx
3.两个值为   1101 10 yy  yyyy  yyyy  /    1101  11xx  xxxx  xxxx
 
在这种处理方式下
如果一旦读取到值为U+D800 ~0xDBFF  ,他必然是代理区间中的一部分,也就非 0号平面中的字符
而且,我们还能够根据值判断出来,到底是高位还是低位,也就是第一个还是第二个数值
只需要取出来这20位,然后再加上0x010000  这就是这个字符的码点
 
可以看得出来,对于基本平面中的字符,使用2个字节长度,16位表示,这被称之为一个代码单元
对于除了基本平面外的辅助平面,使用4个字节长度来表示,也就是两个代码单元
 
之前我们提到过,Unicode中的一个字符的值,被称之为一个码点
显然,一个码点,可能被一个代码单元存储,也可能被两个连续的代码单元存储

UTF-32

UTF-32编码以32位无符号整数为单位
Unicode的UTF-32编码就是其对应的32位无符号整数
32位可以表示的个数为  2的32次方  为4294967296,显然绰绰有余,没什么好说的了

UTF-8

UTF-8 是目前互联网上使用最广泛的一种 Unicode 编码方式,可变长
使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度  
 
规则
可以把编码分解成两部分,head和body
head中记录需要字节的个数,使用第一个字节中1 的个数来表示
body记录真实的数据, 如果需要不止一个字节,那么body自然由多个字节组成,每个body的前两个字节为10  其余为数据
[十]基础数据类型之Unicode编码简介
UTF-8编码的最大长度是4个字节,也就是最多有21个x 表示
Unicode的最大码位0x10FFFF   0001 0000 1111 1111 1111 1111也只有21位
如果一个字节足够表示
只需要一个字节即可表示,那么第一位为0  其余7位用于表示字符编码的值
看得出来明显的好处,可以兼容ASCII 
如果一个字节不够表示,根据范围选择,需要几个字节,就有几个1,然后补一个0   后面的body依次存放数据即可
想要确定一个码点的编码
1. 查看范围,根据上表确定格式
2.转换为对应的二进制序列
3. 替换掉x即可

字节序

根据我们上面描述的utf8 以及utf16都有可能使用不止一个字节进行编码
其实还有很多其他数据也不仅仅是一个字节进行表达
在计算机中最终都是二进制序列的形式
比如utf-16中,虽然我可以根据值确定是否在0号平面内,还是在扩展辅助平面的
但是,如何把一个二进制序列解析为他的值,这是一个问题
比如
0000 0001  0000 0010 假如说这是一个十六进制数
他到底表示的是0102 还是0201? 从哪边开始解读?
人会很自然的把左边当高位,右边当低位, 但是, 计算机, 你必须对他进行说明
这就是字节序的问题
其实就是高位和低位与内存地址高低的对应关系
分为大端排序( Big endian  BE)和小端排序(Little endian  LE)
 
大端排序----高地址存储低位  低地址存储高位
小端排序----高地址存储高位  低地址存储低位
在内存中0x01020304的存储方式
 
内存地址 4000 4001 4002 4003
BE           01     02     03     04
LE           04     03     02     01
 
其实只要记住,大小端说的都是低地址
大端就是低地址存储高位
小端就是低地址存储低位