之前对字符编码一直有疑惑,最近查了一些资料,也看了一些文章,在此进行简单的总结,以便日后深入学习。
Unicode是一种编码标准,它确定了涵盖所有字符的通用字符集(Universal Character Set),为世界上的每个字符都指定了唯一的数字与之对应。
Unicode用0~0x10FFFF来映射世界上的所有字符,共有17 * ( 2 ^ 16) = 17 * 65536 = 1114112个码位(code point),至今这些码位也只有一少部分被使用。
Unicode并不是具体的编码方式,虽然它规定了每个字符对应的码值,但并未规定编码后的字节序列的组织方式,基于Unicode的不同的编码方式都携带相同Unicode码值信息,但字节序列的具体内容和格式却各不相同。
Unicode码值共三个字节,最高字节的取值范围是0~0x10(也就是0~16),共17中可能。Unicode根据最高字节,将码值空间划分为17个层面(Plane)。0号层面称为BMP(Basic Multilingual Plane,基本多文种层面),码值范围为0~0xFFFF(两个字节就可以表示BMP内的所有码值)。我们平常用到的Unicode字符一般都在BMP上。
Unicode中的码值0xFEFF称为BOM(Byte Order Mark,字节顺序标记),也称为ZWNBSP(Zero Width Non-Break Space,零宽非断行空格);码值0xFFFE保留不使用。
大小端
大端(Big Endian):高字节保存在低地址
小端(Little Endian):高字节保存在高地址
UCS-2
这种编码方式使用两个字节的固定长度来表示一个字符,因此它只能表示BMP中的字符
,其值是Unicode码值的低两字节。
UTF-16
UTF-16与UCS-2类似,不同之处在于UCS-2只考虑BMP内的字符,而UTF-16采用了变长编码,每个字符的编码长度为2个字节或4个字节。对于BMP内的字符,UTF-16与UCS-2相同。对于BMP以外的字符,则需要4个字节。
UTF-16/UCS-2以两个字节为编码单元(编码时存储二进制代码的单位),而UTF-8以字节为编码单元。
编码时,编码单元之间的顺序是确定的,但编码单元内部各字节(如果多于一个字节的话)的顺序就不一定了。这就是大小端问题,大小端问题存在于编码单元内部,而不是多个编码单元之间。
由于UTF-8以字节为编码单元,单元内部只有一个字节,所以不存在大小端问题。而UTF-16/UCS-2以两个字节为编码单元,所以它有大小端问题。
为了解决大小端问题,UTF-16/UCS-2在字节序列的最前面加上两个字节,用来标识大小端。FE FF(即BOM)表示大端,FF FE表示小端。
FEFF的UTF-8编码是EF BB BF,虽然UTF-8不需要标识大小端,但在UTF-8编码的字节流前也会加上EF BB BF,用来指明该字节流使用的是UTF-8编码方式。
Windows的txt文件,另存为时可以选择编码方式,如果选择Unicode,那么即是什么也不写,txt文件的大小也是2字节,这两个字节就是用来标识大小端的,使用UltraEdit打开txt,并用16进制方式查看,发现这两个字节的内容是正是FF FE(说明是小端)。同理,若选择UTF-8,那么即是什么也不写,文件的大小也是3字节,以16进制查看,发现这三个字节的内容正是EF BB BF。
Unicode -> UTF-8
UTF-8采用变长编码,根据Unicode码值的不同范围,采用不同长度的编码(1~4字节)
16进制码值 | 对应的UTF-8编码 | 码值所占位数 |
0000 - 007F | 0XXXXXX | 7bit |
0080 - 07FF | 110XXXXX 10XXXXXX | 11bit |
0800 - FFFF | 1110XXXX 10XXXXXX 10XXXXXX | 16bit |
10000 - 10FFFF | 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX | 21bit |
找到Unicode码值所属的区间,把码值写成二进制,替换模板中的“X”,即可得到UTF-8编码
Unicode -> UFT-16
设某个字符的Unicode码值是U
如果U < 0x10000,那么U的UFT-16编码就是U对应的16位无符号整数
如果U >= 0x10000,则先计算U’ = U - 0x10000(易知U’的取值范围是0~0xFFFFF),然后将U’写成二进制形式:YYYY YYYY YYXX XXXX XXXX
那么U的UTF-16编码的二进制表示就是:110110YYYYYYYYYY 110111XXXXXXXXXX
为了区分2个字节的UFT-16编码和4个字节的UTF-16编码,所以2个字节的UFT-16编码不能以110110或110111开头。因此,Unicode的设计者把0xD800~0xDFFF保留下来,并成为代理区。