`
mmdev
  • 浏览: 12925634 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Unicode编码及其实现:UTF-16、UTF-8,and more

 
阅读更多

田海立@CSDN

2012-04-25


本文主要讨论Unicode的编码及其各种实现,着重讨论UTF-16,UTF-8的实现规则,以及Big-endian和Little-Endian的存储顺序。


一、Unicode编码


Unicode出现之前已经有各种编码标准:ANSI、ISO8859-1、GB2312、GBK以及BIG-5等。Unicode试图统一各种编码,在Unicode演进过程中,也有自身不断修复的过程:刚开始的时候用16位表达65535个字符,认为已经足够收集所有的字符;后来随着大量中文、韩文和日文等表意文字的加入,已经超出了65535个字符,16位已经不能描述所有的字符集了。

在Unicode字符集中的某个字符对应的代码值,称作代码点(Code Point),用16进制书写,并加上U+前缀。比如,‘田’的代码点是U+7530;‘A’的代码点是U+0041。

Unicode定义的字符集已经超过16位所能表达的范围,把所有这些CodePoint分成17个代码平面(Code Plane)

  • U+0000 ~ U+FFFF划入基本多语言平面(Basic MultilingualPlane,简记为BMP);
  • 其余划入16个辅助平面(Supplementary Plane),代码点范围U+10000 ~ U+10FFFF

虽然这样划分,但并不是每个Plane中的Code point都对应有字符,这里面有保留的,还有特殊用途的。


二、Unicode编码的实现


Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的,但是在实际存储和传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。

对Unicode编码的主要有UTF-16BE、UTF-16LE、UTF-8、UTF-7以及UTF-32等实现方式,目前常用的实现方式是UTF-16LE、UTF-16BE和UTF-8。


2.1 UTF-16

UTF-16是用16bit编码来表达Unicode,这样表达范围是216(即65536),也就是UTF-16的代码单元(Code Unit)为16bits。如果表达BMP内的字符,用一个UTF-16的Code Unit就可表达,对于辅助平面内的字符,UTF-16有巧妙的设计。

落在BMP内,从U+D800U+DFFF之间的Code Point区段是永久保留不映射到字符, UTF-16利用这保留下来的0xD800-0xDFFF区段的CodePoint来对辅助平面内的字符的Code Point进行编码。


对U+0000.. U+D7FF以及U+E000.. U+FFFF的编码

UTF-16与UCS-2对这个范围内的CodePoint进行编码,采用单个16bit长的CodeUnit,数值等价于对应的Code Point。BMP中的这些Code Point是仅有的可以被UCS-2表示的Code Point。

对U+10000.. U+10FFFF的编码

辅助平面(Supplementary Planes)中的CodePoint,在UTF-16中被编码为一对16bit长的Code Unit(即32bit,4Bytes),称作代理对(surrogate pair)。


具体方法是:

UTF-16解码

hi\lo

DC00

DC01

DFFF

D800

10000

10001

103FF

D801

10400

10401

107FF

DBFF

10FC00

10FC01

10FFFF

  1. Code Point减去0x10000, 得到的值是长度为20bit(0..0xFFFFF);
  2. 步骤1得到数值的高位的10比特的值(值范围为0..0x3FF)被加上0xD800得到第一个Code Unit或称作高位代理(high surrogate)或前导代理(lead surrogate)。取值范围是0xD800..0xDBFF
  3. 步骤1得到数值的低位的10比特的值(值范围为0..0x3FF)被加上0xDC00得到第二个Code Unit或称作低位代理(low surrogate)或后尾代理(trail surrogate)。取值范围是0xDC00..0xDFFF

这样,这个范围内的字符就被编码成了一个代理对[lead surrogate,trail surrogate]:两个16bits的Code Unit,取值范围分别是0xD800..0xDBFF和0xDC00..0xDFFF。而BMP中得到的Code Unit的范围是0x0000..0xFFFF(0xD800..0xDFFF是保留的,不包含其中),所以这三个区段是相互不重叠的,在解码时很容易实现。

UTF-16解码[高位代理+低位代理]得到的Code Unit对与Code Point的对应关系如上表所示。


下面以对U+64321的UTF-16编码为例,看一下对于辅助平面内的字符是如何编码的:

V = 0x64321

Vx = V - 0x10000

= 0x54321

= 01010100 0011 0010 0001

Vh = 01 0101 0000 // Vx 的高位部份的 10 bits

Vl = 11 0010 0001 // Vx 的低位部份的 10 bits

w1 = 0xD800 // 结果的前16位元初始值

w2 = 0xDC00 // 结果的后16位元初始值

w1 = w1 | Vh

= 1101 1000 0000 0000

| 01 0101 0000

= 1101 1001 0101 0000

= 0xD950

w2 = w2 | Vl

= 1101 1100 0000 0000

| 11 0010 0001

= 1101 1111 0010 0001

= 0xDF21


所以,这个字 U+64321 最终的 UTF-16 编码是:

0xD950 0xDF21


UTF-16的Code Unit是16bits,两个字节。存储一个Code Unit的时候,还有存取的先后顺序问题,也就是Endian问题,这在后面章节讲述。


2.2 UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,使用一至四个字节为每个字符编码:

  • Unicode范围为U+0000..U+007F 的128个ASCII字符只需一个字节编码;
  • Unicode范围为U+0080..U+07FF的字符需要二个字节编码;
  • Unicode范围为U+0800..U+FFFF的其他BMP中的字符(这包含了大部分常用字)使用三个字节编码;
  • Unicode 辅助平面的字符(其他极少使用的字符)使用四字节编码。

对上述提及的第四种字符而言,UTF-8使用四个字节来编码似乎太耗费资源了。但UTF-8对所有常用的字符都只用三个字节表达,而且UTF-16编码对前述的第四种字符同样需要四个字节来编码,而如果是ASCII居多的字符,UTF-8能极大的节约存储空间。UTF-8逐渐成为电子邮件、网页及其他储存或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。互联网邮件联盟(IMC)建议所有电子邮件软件都支持UTF-8编码。


对CodePoint各个范围内的字符进行UTF-8编码的规则如下:

Code point

UTF-8字节流

U+00000000 – U+0000007F

0xxxxxxx

U+00000080 – U+000007FF

110xxxxx 10xxxxxx

U+00000800 – U+0000FFFF

1110xxxx 10xxxxxx 10xxxxxx

U+00010000 – U+001FFFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

其中,U+D800到U+DFFF之间的区段在Unicode字符集的定义中没有具体字符使用的,被用来在UTF-16编码中对辅助平面的字符进行编码。


下面以“田”(Code Point为U+7530)为例,看如何对其进行UTF-8编码:

  • U+7530落在U+0800..U+FFFF区间,采用三字节编码;
  • 0x7530转换为二进制为111 010100 110000
  • 代入表中,得到111001111001010010110000

这样,得到“田”(U+7530)的UTF-8编码:0xE7 94 B0。


知道UTF-8的编码规则,我们可以对于UTF-8编码中的任意字节B,进行下面解码:

  • 如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码(字符的第一个字节之外的后编码);
  • 如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
  • 如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
  • 如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示。

2.3 UCS-2 vs UTF-16,UCS-4 vs UTF-32

UCS-2每个字符占用2个字节。UCS-2是UTF-16的子集。在没有辅助平面前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,UTF-16加入了对辅助平面内的字符的支持。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不支持UTF-16中超过2bytes的字集。亦即,对于小于0x10000的UCS码,UTF-16编码就等于UCS码。Java早期版本对Unicode的支持,就只是UCS-2的支持,现在加入了对UTF-16的完整支持。

UCS-4UTF-32的意义一致,对每个字符都使用4字节(31位字符集,加上恒为0的首位,共需占据32位)。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。虽然每一个Code Point使用固定长定的字节看似方便,对于普通只需要2个字节存储的常用字占绝大对数的字符集来说,却极大的浪费了空间,并没怎么得到应用。


三、Big-Endian/Little-Endian与BOM

在讲UTF-16编码方式时说到,UTF-16编码的Code Unit是2个字节,这两个字节在传输和存储过程中,高/低位位置不同,是不同的字符。比如,“田”的UTF-16编码是0x7530,但是如果存成0x3075,就变成了“ふ”,成了另外的字符。

所以,为了识别一个编码过的字符的存储顺序,必须用特殊字符来指示。Unicode字符中U+FEFF被用来指示这种存储顺序,被称作Byte Order Mark(BOM)。

  • Big-Endian:最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。
  • Little Endian:最低位地址存放低位字节,可称低位优先,内存从最低地址开始按顺序存放(低数位数字先写)。最低位字节放最前面。

BOM在Big-Endian系统上存储为FE FF;而在Big-Endian系统上存储则为FF FE。所以在以Big-Endian存储的UTF-16(UTF-16BE)的文件的开头,用FEFF指示;以Little-Endian存储的UTF-16(UTF-16LE)的文件的开头,用FFFE指示。

BOM的UTF-8编码为111011111011101110111111(EF BB BF),所以一般EF BB BF被放在文本的开头,用来指示其编码为UTF-8。

四、Unicode编码实践


在Windows的文本编辑工具记事本上,选择“另存为”的时候,用户可以选择不同的编码选项,对应编码选项有“ANSI”,“Unicode”,“Unicode big endian”,以及“UTF-8”。因为Windows的存储方式是Little-Endian,所以“Unicode”,“Unicode big endian”对应的分别是UTF-16LE和UTF-16BE。

读者可以试着编写一串字符,然后分别用不同的编码保存,再用可以16进制编写的纯文本编辑工具(如,Ultra-edit)来检验一下具体的编码实现和存储顺序。下面是笔者将“田海立(U+7530, U+6D77, U+7ACB)”以不同编码方式保存,得到的结果:

田海立_UTF-16BE.txt

FEFF75306D777ACB

田海立_UTF-16LE.txt

FFFE3075776DCB7A

田海立_UTF-8.txt

EFBBBFE794B0E6B5B7E7AB8B

为了明确起见,BOM的编码用粗体标注;田的编码用红色标注;海的编码用绿色标注;立的编码用蓝色标注。可以看到,记事本(Notepad)存储的Unicode编码的文件的开头位置,用BOM的相应编码指示了编码格式。


【后记】历史

最近需要用到Unicode的编码实现方式,又收集了一下资料。发现早在06年的时候,笔者就准备总结一下Unicode的编码实现,文档里也已经有了提纲。现在也不记得当时什么原因给耽搁了,好在现在及时总计归纳。好脑子不如烂笔头啊。如果当初总结下来,现在也不用再浪费时间收集资料。

希望,这次的总结能比较完善,以后再用到Unicode编码,只要参考此文即可!(当然前提是Unicode标准别又演进了^_^)


【附】基本概念对照

  1. Code Point代码点或码位
  2. Code Unit代码单元或码元,是指一个已编码的文本中具有最短的比特组合的单元。对于UTF-8来说,码元是8比特长;对于UTF-16来说,码元是16比特长;对于UTF-32来说,码元是32比特长。
  3. BMP - Basic Multilingual Plane
  4. UTF - Unicode Transformation Format
  5. BOM – Byte Order Mark
  6. UCS - Universal Character Set


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics