unicode编码

来源:互联网 发布:知乎神州普惠待遇 编辑:程序博客网 时间:2024/06/08 04:57

https://my.oschina.net/goldenshaw/blog/310331
http://www.freebuf.com/articles/others-articles/25623.html
http://www.ruanyifeng.com/blog/2014/12/unicode.html
http://blog.csdn.net/stephen1315/article/details/7476236

unicode编码

一,历史回顾

​ 很久以前,计算机制造商有自己的表示字符的方式。他们并不需要担心如何和其它计算机交流,并提出了各自的方式来将字形渲染到屏幕上。随着计算机越来越流行,厂商之间的竞争更加激烈,在不同的计算机体系间转换数据变得十分蛋疼,人们厌烦了这种自定义造成的混乱。最终,计算机制造商一起制定了一个标准的方法来描述字符。ANSI的ASCII字符集,使用一个字节的低7位来表示字符,总共表示128个字符,其中包括了英文字母、数字、标点符号等常用字符。但是一个字节是8个比特,这意味着1个比特并没有被使用,也就是从128到255的编码并没有被制定ASCII标准的人所规定,这些美国人对世界的其它地方一无所知甚至完全不关心。
​ 其它国家的人趁这个机会开始使用128到255范围内的编码来表达自己语言中的字符。例如,144在阿拉伯人的ASCII码中是گ,而在俄罗斯的ASCII码中是ђ。即使在美国,对于未使用区域也有各种各样的利用。IBM PC就出现了“OEM 字体”或”扩展ASCII码”,为用户提供漂亮的图形文字来绘制文本框并支持一些欧洲字符,例如英镑(£)符号。这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。
​ 再强调一遍,ASCII码的问题在于尽管所有人都在0-127号字符的使用上达成了一致,但对于128-255号字符却有很多很多不同的解释。你必须告诉计算机使用哪种风格的ASCII码才能正确显示128-255号的字符
​ 这对于北美人和不列颠群岛的人来说不算什么问题,因为无论使用哪种风格的ASCII码,拉丁字母的显示都是一样的。英国人还需要面对的问题是原始的ASCII码中不包含英镑符号,但是这个已经无关紧要了。与此同时,在亚洲有更让人头疼的问题。亚洲语言有更多的字符和字形需要被存储,一个字节已经不够用了。所以他们开始使用两个字节来存储字符,这被称作DBCS(双字节编码方案)
​ 这些问题成为了系统开发者的噩梦。例如,MS DOS必须支持所有风格的ASCII码,因为他们想把软件卖到其他国家去。他们提出了「内码表」这一概念。例如,你需要告诉DOS(通过使用”chcp”命令)你想使用保加利亚语的内码表,它才能显示保加利亚字母。内码表的更换会应用到整个系统。这对使用多种语言工作的人来说是一个问题,因为他们必须频繁的在几个内码表之间来回切换。
​ 尽管内码表是一个好主意,但是它不是一个简洁的解决方案,它只是一个hack技术或者说是简单的修正来让编码系统可以工作。

二,进入Unicode的世界
​ 最终,美国人意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,以便缓解程序员的痛苦和避免字符编码引发的第三次世界大战。出于这个目的,Unicode诞生了。
​ Unicode背后的想法非常简单,将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了。它从0开始,为每个符号指定一个编号,这叫做”码点”(code point一种抽象的数字编号)。比如,码点0的符号就是null(表示所有二进制位都是0)

U+0000 = null

上式中,U+表示紧跟在后面的十六进制数是Unicode的码点。
比如,中文”好”的码点是十六进制的597D

U+597D =

​它的范围目前是U+0000~U+10FFFF,理论大小为10FFFF+1=110000 。后一个1代表是65536,因为是16进制,所以前一个1是后一个1的16倍,所以总共有1×16+1=17个的65536的大小,粗略估算为17×6万=102万,所以这是一个百万级别的数。110000写成二进制是100010000000000000000,是一个21位的二进制数。
这么多符号,Unicode不是一次性定义的,而是分区定义。每个区可以存放65536个( 216 )字符,称为一个平面(plane)。目前,一共有17个(25)平面,也就是说,整个Unicode字符集的大小现在是221
最前面的65536个字符位,称为基本平面(缩写BMP),它的码点范围是从0一直到2161,写成16进制就是从U+0000到U+FFFF。所有最常见的字符都放在这个平面,这是Unicode最先定义和公布的一个平面。
剩下的字符都放在辅助平面(缩写SMP),码点范围从U+010000~U+10FFFF

Unicode编码规则
  1. 216(65536)个号码组成一个平面(plain)
  2. 目前共有17个平面,整个空间大小:221
  3. 一个基本平面(BMP),U+0000~U+FFFF
  4. 16个辅助平面(SMP):U+010000~10FFFF
    Unicode误解:Unicode支持的字符上限是65536个,Unicode字符必须占两个字节

Unicode只是一个用来映射字符和数字的标准。它对支持字符的数量没有限制,也不要求字符必须占两个、三个或者其它任意数量的字节。Unicode并不涉及字符是怎么在字节中表示的,它仅仅指定了字符对应的数字,仅此而已

Unicode的问题

Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字”严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。2)Unicode在很长一段时间内无法推广,直到互联网的出现。

Unicode的编码和实现

unicode编码系统可分为编码方式和实现方式两个层次,常用汉字 的unicode 码范围是:\u4e00-\u9fa5

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的
2)对于n字节的符号(n>1),第一个字节前n位都设为1第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式(十六进制) | (二进制)------------------------------------------------0000 0000-0000 007F | 0xxxxxxx0000 0080-0000 07FF | 110xxxxx 10xxxxxx0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

跟据上表,解读UTF-8编码非常简单。
如果一个字节的第一位是0,则这个字节单独就是一个字符;
如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字”严”为例,演示如何实现UTF-8编码
已知”严”的unicode是4E25(100111000100101)
根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF)
因此”严”的UTF-8编码需要三个字节,即格式是”1110xxxx 10xxxxxx 10xxxxxx”。然后,从”严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,”严”的UTF-8编码是”11100100 10111000 10100101”,转换成十六进制就是E4B8A5。

Unicode与UTF-8之间的转换

通过上个例子,可以看到”严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe。打开文件后,点击”文件”菜单中的”另存为”命令,会跳出一个对话框,在最底部有一个”编码”的下拉条。

里面有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8
1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)
2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式
3)Unicode big endian编码与上一个选项相对应。
4)UTF-8编码
选择完”编码方式”后,点击”保存”按钮,文件的编码方式就立刻转换好了

Little endian和Big endian

Unicode码可以采用UCS-2格式直接存储。以汉字”严”为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。第一个字节在前,就是”大头方式”(Big endian),第二个字节在前就是”小头方式”(Little endian)。那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

下面,举一个实例。
打开”记事本”程序Notepad.exe,新建一个文本文件,内容就是一个”严”字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。
然后,用文本编辑软件UltraEdit中的”十六进制功能”,观察该文件的内部编码方式。
1)ANSI:文件的编码就是两个字节”D1 CF”,这正是”严”的GB2312编码,这也暗示GB2312是采用大头方式存储的。
2)Unicode:编码是四个字节”FF FE 25 4E”,其中”FF FE”表明是小头方式存储,真正的编码是4E25。
3)Unicode big endian:编码是四个字节”FE FF 4E 25”,其中”FE FF”表明是大头方式存储。
4)UTF-8:编码是六个字节”EF BB BF E4 B8 A5”,前三个字节”EF BB BF”表示这是UTF-8编码,后三个”E4B8A5”就是”严”的具体编码,它的存储顺序与编码顺序是一致的

UTF-32简介

Unicode只规定了每个字符的码点,到底用什么样的字节序表示这个码点,就涉及到编码方法。
最直观的编码方法是,每个码点使用四个字节表示,字节内容一一对应码点。这种编码方法就叫做UTF-32。比如,码点0就用四个字节的0表示,码点597D就在前面加两个字节的0

 U+0000 = 0x0000 0000 U+597D = 0x0000 597D

UTF-32的优点在于,转换规则简单直观,查找效率高。缺点在于浪费空间,同样内容的英语文本,它会比ASCII编码大四倍。这个缺点很致命,导致实际上没有人使用这种编码方法,HTML 5标准就明文规定,网页不得编码成UTF-32

UTF-16简介

UTF-16编码介于UTF-32与UTF-8之间,同时结合了定长和变长两种编码方法的特点
它的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)

UTF-16编码变长编码法长度为2个字节或4个字节-----------------------------------------------------编码范围            字节0x0000-0xFFFF       20x010000-10FFFF     4

于是就有一个问题,当我们遇到两个字节,怎么看出它本身是一个字符,还是需要跟其他两个字节放在一起解读?
在基本平面内,从U+D800到U+DFFF是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。

具体来说,辅助平面的字符位共有220个,也就是说,对应这些字符至少需要20个二进制位。UTF-16将这20位拆成两半,前10位映射在U+D800到U+DBFF(空间大小210),称为高位(H)后10位映射在U+DC00到U+DFFF(空间大小210),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

utf-16编码规则Unicode编号0xD800-0xDFFF为空段编号大于0xFFFF的字段>一半映射在0xD800-0xDBFF>一半映射在0xDC00-0xDFFF

所以,当我们遇到两个字节,发现它的码点在U+D800到U+DBFF之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00到U+DFFF之间,这四个字节必须放在一起解读。

UTF-16的转码公式

Unicode码点转成UTF-16的时候,首先区分这是基本平面字符,还是辅助平面字符。如果是前者,直接将码点转为对应的十六进制形式,长度为两字节

U+597D = 0x597D

如果是辅助平面字符,Unicode 3.0版给出了转码公式

H = Math.floor((c-0x10000) / 0x400)+0xD800L = (c - 0x10000) % 0x400 + 0xDC00

UCS-2编码

怎么突然杀出一个UCS-2?这就需要讲一点历史。
互联网还没出现的年代,曾经有两个团队,不约而同想搞统一字符集。一个是1988年成立的Unicode团队,另一个是1989年成立的UCS团队。等到他们发现了对方的存在,很快就达成一致:世界上不需要两套统一字符集。

1991年10月,两个团队决定合并字符集。也就是说,从今以后只发布一套字符集,就是Unicode,并且修订此前发布的字符集,UCS的码点将与Unicode完全一致。

UCS的开发进度快于Unicode,1990年就公布了第一套编码方法UCS-2,使用2个字节表示已经有码点的字符。(那个时候只有一个平面,就是基本平面,所以2个字节就够用了。)UTF-16编码迟至1996年7月才公布,明确宣布是UCS-2的超集,即基本平面字符沿用UCS-2编码,辅助平面字符定义了4个字节的表示方法。

两者的关系简单说,就是UTF-16取代了UCS-2,或者说UCS-2整合进了UTF-16。所以,现在只有UTF-16,没有UCS-2。

0 0