前言:最近在学习一个关于Emoji的开源项目,学习过程中了解到Emoji是Unicode字符集中的一部分。平时工作、学习中经常会碰到Unicode、UTF-8等等这些字符编码相关的名词,往往不容易搞清楚之间的关系。于是便用了一些时间学习了Unicode的相关知识。本篇文章作为Unicode的学习笔记。
在了解Emoji的过程中,发现了一个网站Emoji Unicode Tables,该网站提供了Emoji表情与Unicode字符集的对应关系。
如下图所示(图一):
点击表中的Unicode一栏,页面跳转到如下页面(图二):
该页面详细显示了该Emoji对应的Unicode值以及UTF-8、UTF-16 LE、Sorrogates编码,但是它们之间是如何转换的,以及它们之间有着怎样的关系。
一、Unicode
Unicode(统一码、万国码、单一码)是计算机科学领域里一项业界标准。它对世界上的大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。Unicode为每一个符号指定一个独一无二的编码。
Unicode目前的编码范围为0x0000~0x10FFFF,并将该范围分为17个平面,每个平面拥有65536(2^16)个码位,共1114112个,但是目前只用了少数的几个平面。其中最重要的一个平面为平面0(Basic Multilingual Plane,简称BMP),该平面的编码范围为0x0000~0xFFFF,包含了世界上常用的语言文字和符号,是Unicode最基础和常用的部分。
- BMP中的字符如果用UTF-8编码,需要1-3个字节,UTF-16编码表示需要2个字节
- BMP以外的字符的字符以UTF-8编码需要4个字节,UTF-16编码需要4个字节
- 另外还有一种UTF-32编码,该编码使用较少,UTF-32对任意Unicode字符编码都需要4个字节
- BMP中0xD800~0xDFFF范围内共2048个码位没有对应任何字符,该范围称为Surrogate。该范围又分为两段,0xD800~0xDBFF包含的1024个码位称为high surroagte,0xDC00~0xDFFF包含的1024个码位成为low surroagte,可以通过这两个范围组成四个字节的surroagte pair来一一对应到BMP之外的字符。
那么Unicode和UTF-8、UTF-16是什么关系呢?简单的说,Unicode是字符集而UTF-8这些是Unicode的不同编码存储方式。
二、UTF-8
UTF-8使用1~4个字节表示一个符号,不同的符号所占的字节数不同,所以UTF-8也称为可变长编码。
UFT-8的编码方式很简单,只有两条:
1. 对于单个字节,字节的第一位为0,剩下的7位为该符号的unicode码。对于英文字母来说,其UTF-8编码和ASCII编码相同。
2. 对于长度为n(n >1)的字节,第一个字节的前n位都为1,第n+1位为0。接下来的字节,每个字节的前两位均为10。剩下的位数使用该符号unicode码的二进制填充。
Unicode编码范围(16进制) | UTF-8编码(2进制) |
---|---|
0x0000 0000~0x0000 007F | 0xxxxxxx |
0x0000 0080~0x0000 07FF | 110xxxxx 10xxxxxx |
0x0000 0800~0x0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0x0001 0000~0x0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
根据上表编码规则很容易对UTF-8进行解读。如果第一位为0,则该单个字节就表示一个字符;如果第一位为1,则有多少连续的1就表示该字符占几个字节。
现在以“中文”的“中”为例,“中”的unicode码为4E2D(100111000101101)属于上表中的第三行范围,所以”中”的UTF-8编码应该占3个字节(1110xxxx 10xxxxxx 10xxxxxx),用“中”的unicode码的二进制位进行填充,不足的位用0补齐。所以“中”的UTF-8编码为E4 B8 AD(11100100 10111000 10101101)。
三、UTF-16
UTF-16为Unicode的另一种编码方式。位于BMP(范围0x0000~0xFFFF)中的符号,UTF-16编码可直接使用该字符的unicode码直接表示,这时UTF-16编码占2个字节。位于BMP外的符号,如果需要用UTF-16编码表示,需要将unicode码转换为四个字节的代理对(surroagte pair)来表示。
其转换过程如下:
如图,具体过程如下:
- 需要转换使用代理对表示,说明该符号unicode的码值在0xFFFF<x<=0x10FFFF内
- 用该码值减0x10000,得到的结果范围为0x0~0xFFFFF,可使用20bit进行表示。
- 用该值的前10bit与0xD800相加,后10bit与0xDC00相加,最后得到的一个24bit的代理对,可以用四个字节表示。
如本篇文章的图二所示,该Emoji符号的Unicode值为U+1F601,范围在0xFFFF<x<=0x10FFFF内,所以可以转换为代理对进行表示,U+1F601减去0x10000的值为0xF601,二进制表示为00001111011000000001。用0xD800加前10bit,即0xD800 + 0b0000111101,结果为0b1101100000111101。后10bit与0xDC00相加,即0xDC00 + 0b1000000001,结果为0b1101111000000001。转化为四个的字节的16进制为D83D DE01,即图二中的Surrogates值。
四、Little endian和Big endian
Unicode码可以采用UCS-2格式直接存储。以汉字”严”为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
因此,第一个字节在前,就是”大头方式”(Big endian),第二个字节在前就是”小头方式”(Little endian)。
那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
结语,到此为止图二表格中所有的值,已经都可以读懂了:)
参考: