编码研究笔记

本次讨论的编码涉及到Unicode、传统的ascii、和ascii的扩展集ansi中的中国编码、西文编码。java

老实说老师都一直以ascii编码讲解文字的编码,汉子编码则是一笔带过,害我在很长时间误认为电脑世界中只有一种编码,若是我是个以英语为母语的人的话,好像没什么问题,不巧我是个中国人,就不可能靠这种误解顺利认识整个编码世界windows

编码样式很是之多,但经常使用的也就那么几个,其中以我国为例,国内经常使用的编码格式有gb23十二、gbk、utf-八、utf-1六、iso-8859-1,其中iso-8859-1格式的普遍应用,并非由于他多么优秀,相反,他只对西文字符编码,彻底不可能表示汉子,对国内来讲,这有时是个灾难,只是由于国内的软件产品大都是从国外传递过来的,他们采用的编码格式默认就会是iso-8859-1。网络

这里首先解释一个概念性的问题,编码方式和实现方式这两个概念的区别,编码方式是指对数字进行组织,选取一个具体的范围,而后将范围中的数字设置为码位,为每一个码位编制一个文字符号,这样文字符号就和数字创建了一一对应的关系,而实现方式则是指将数字转换到程序数据的编码方案。举个例子,unicode为编码方式,而utf-八、utf-16则为实现方式。要知道unicode是用0-0x10FFFF文字范围来进行文字符号映射的,而计算机则是以二进制八位为基本单位的,若是能让程序识别,显然须要一个将编码方式映射具体字符的数字经过某种方式转化为程序数据,所以实现方式就应运而生了,utf-八、utf-16就产生了。编码

咱们先从如今世界范围内应用最广的unicode开始,概念性的东西咱就一笔带过吧,Unicode是国际组织制定的能够容纳世界上全部文字和符号的字符编码方案,unicode编码范围为0-0x10FFFF,能够看到若是用二进制表示,最大值0x10FFFF其实是spa

10000  11111111  11111111,最多能够容纳1114112个字符,这里要澄清一个你们可能会产生模糊的概念意义,UCSUnicode的关系,通用字符集(Universal Character SetUCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS-2用两个字节编码,UCS-44个字节编码。而Unicode 是基于通用字符集(Universal Character Set)的标准来发展,历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目,后者开发的统一码项目。所以最初制定了不一样的标准。设计

1991年先后,两个项目的参与者都认识到,世界不须要两个不兼容的字符集。因而,它们开始合并双方的工做成果,并为创立一个单一编码表而协同工做。从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得二者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都赞成保持二者标准的码表兼容,并紧密地共同调整任何将来的扩展。在发布的时候,Unicode通常都会采用有关字码最多见的字型,但ISO 10646通常都尽量采用Century字型。代理

Unicode在实现上仍是和UCS有一些差距的,标准UCS-4根据最高位为0的最高字节分红2^7=128group。每一个group再根据次高字节分为256个平面(plane)。每一个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称做BMPBasic Multilingual Plane)。将UCS-4BMP去掉前面的两个零字节就获得了UCS-2。每一个平面有2^16=65536个码位。Unicode计划使用了17个平面(注意unicode并只是用了group0),一共有17*65536=1114112个码位。在Unicode 5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000-0xFFFFD0x100000-0x10FFFD。所谓专用区,就是保留给你们放自定义字符的区域,能够简写为PUAcode

  平面0也有一个专用区:0xE000-0xF8FF,有6400个码位。平面0的0xD800-0xDFFF,共2048个码位,是一个被称做代理区(Surrogate)的特殊区域。代理区的目的用两个UTF-16字符表示BMP之外的字符。这个待会再解释。orm

       注意,在Unicode 5.0.0版本中,238605-65534*2-6400-2048=99089。余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,它们对应着Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080341943253337个字符。平面243253个字符都是汉字。平面0上定义了27973个汉字。能够很清楚的看到,平面2也有不少汉字,咱们经常使用的汉字大都在平面0,但若是咱们要用平面2的汉字时,就意味着这个码数用两个二进制的字节是装不下的,而两个字节是utf-16的代码单元的大小,显然这须要必定的转换手段,并且,能够很肯定的是,这个汉字是不能用双字节表示的,因此要注意,由于java内部用的编码方式就是utf-16,双字节是常态,但若是高于两个字节就可能出现什么问题了,好比java中的char基本类型实际上大小为两个字节,当他要表示一个大于两个字节的代码点时,显然,是不可表示的,因此应该认识到,char类型并不能表示全部的字符,他只能表示平面0中的字符,对于其余平面的字符,他就无能为力了,所以,应该慎用char类型,建议用String替代char类型。utf-8

       先来说解utf-16的实现方式,UTF-16编码以16位无符号整数为单位。咱们把Unicode编码记做U。编码规则以下:

  若是U<0x10000,U的UTF-16编码就是U对应的16位无符号整数

  若是U≥0x10000,咱们先计算U'=U-0x10000,而后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

为何U'能够被写成20个二进制位?Unicode的最大码位是0x10ffff,减去0x10000后,U'的最大值是0xfffff,因此确定能够用20个二进制位表示。例如:Unicode编码0x20C30,减去0x10000后,获得0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就获得:1101100001000011 1101110000110000,即0xD843 0xDC30。

  按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

  为了将一个WORD的UTF-16编码与两个WORD的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate):

D800-DB7F ║ High Surrogates ║ 高位替代

DB80-DBFF ║ High Private Use Surrogates ║ 高位专用替代

DC00-DFFF ║ Low Surrogates ║ 低位替代

  高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。那么,高位专用替代是什么意思?咱们来解答这个问题,顺便看看怎么由UTF-16编码推导Unicode编码。

  若是一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?咱们知道第二个WORD的取值范围是0xDC00-0xDFFF,因此这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。咱们将这个范围写成二进制:

1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

  按照编码的相反步骤,取出高低WORD的后10位,并拼在一块儿,获得

1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111  XML即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,获得0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。由于Unicode标准将平面15和平面16都做为专用区,因此0xDB80到0xDBFF之间的保留码位被称做高位专用替代。

再次注意,非BMP的文字字符在用utf-16表示时是用四个字节表示的,也即双字,在java中,utf-16的代码单元是两个字节,也即以char为基本单位,而unicode中,每个码位叫作一个代码点,通常在字符码数在BMP下时,一个代码点对应一个代码单元,可是若是超过BMP,这就不会是一一对应的关系了,在这里还要说一个题外话,一个代码点可能对应者多个代码单元,哪个代码点能够表示一个字符吗?这在unicode中是能够的,但在latin-1也即iso-8859-1中这可能就存在变数了,latin-1能够看作是对传统ascii的扩展,其采用单字节编码方式,所以,其最大能表示255个字符,这对西文字符看起来足够了,可是还会有一些特殊字符或者将来可能诞生的字符,所以他们会采用多个代码点表示一个字符,这些字符多是一个组合字符

  下面讲解utf-8,

UTF-8以字节为单位对Unicode进行编码。从UnicodeUTF-8的编码方式以下:

Unicode编码(16进制) ║ UTF-8 字节流(二进制)

000000 - 00007F ║ 0xxxxxxx

000080 - 0007FF ║ 110xxxxx 10xxxxxx

000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx

010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特色是对不一样范围的字符使用不一样长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码彻底相同。UTF-8编码的最大长度是4个字节。从上表能够看出,4字节模板有21个x,便可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

注意首字节的标志位,有几个1就表明有几个字符,标志位中的0做为标志位的结尾标志

例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,获得:11100110 10110001 10001001,即E6 B1 89。

  例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,获得:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。

下面对编码unicode作一个整体的归纳,如今用的最多的是utf-8,由于对于至少双字节的utf-16来讲,灵活字节数可变的编码方式能够有效的减小字节数,这对于在网络带宽有限的今天,这是颇有意义的,并且,计算机中的不少数据是用西文字符表示的,utf-8在表示西文时只用了一个字符,这显然会比双字节的utf-16有优点,还有一点,utf-8的编码方式有助于自己代码点的校验,能够不用借助外界的校验方式,在复杂多变的网络世界,出错是常态,采用自己的特色校验能够有效的将损失降至最低,由于一个字节的缺失只会对当前代码点有影响,能够很容易定位到出错代码点,对其余代码点没有影响,而utf-16则没有这种特性,若是出现字节缺失,出错点后的字节可能都会解码都会出现问题,与外界校验想比,utf-8能够达到细粒化的校验,而外界校验则是整体性的,具备不肯定性,并且外界校验一旦出错,就直接丢弃,而utf-8则是能够继续解码的,固然utf-8也是优缺点的,仔细想一想,其编码解码方式都是至关耗时的,相对于utf-8,utf-16编码解码方式则很是简单,对于以效率为主要目并且出错率很底的的单机,utf-16显然更具备优点。

下面讨论常见的BOM也即字节序问题,根据字节序的不一样,UTF-16能够被实现为UTF-16LE或UTF-16BE

那么,怎么判断字节流的字节序呢?Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被做为BOM的字符"零宽无中断空格"。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)在Unicode中都是未定义的码位,不该该出如今实际传输中。下表是各类UTF编码的BOM:

UTF编码 ║ Byte Order Mark

UTF-8 ║ EF BB BF

UTF-16LE ║ FF FE

UTF-16BE ║ FE FF

事实上,utf-8是没有字节序问题的,所以BOM可加可不加,所以会有utf-8和utf-8 without BOM,注意这二者是等价的,没有所谓的常态、特殊态,utf-8带BOM也是有好处的,咱们能够经过BOM来识别其是哪一种编码方式,固然,这仅仅限定在unicode系列,utf-16是存在字节序问题的,所以其必定会有BOM

下面讲解GBK 和GB2312

下面为Copy的概念

GBK即汉字内码扩展规范,K为扩展的汉语拼音中“扩”字的声母。英文全称Chinese Internal Code SpecificationGBK编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。GB2312码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集——基本集》,1980年由国家标准总局发布。基本集共收入汉字6763个和非汉字图形字符682个,通行于中国大陆。新加坡等地也使用此编码。GBK是对GB2312-80的扩展,也就是CP936字码表 (Code Page 936)的扩展(以前CP936GB 2312-80如出一辙)。

GBK采用双字节表示,整体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。总计23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号883 个。P-Windows3.2和苹果OS以GB2312为基本汉字编码, Windows 95/98则以GBK为基本汉字编码。

 字符有一字节和双字节编码,00–7F范围内是一位,和ASCII保持一致,此范围内严格上说有96个字符和32个控制符号。

  以后的双字节中,前一字节是双字节的第一位。整体上说第一字节的范围是81–FE(也就是不含80和FF),第二字节的一部分领域在40–7E,其余领域在80–FE。

Copy完毕

注意,gb系列是兼容ascii的,基本上全部的编码格式都是兼容ascii,即便双字节的utf-16也是高位为0,低位为ascii的,要知道在ascii中,0是无心义的,所以,即便文字采用其余编码格式编码,采用ascii解码都是能够看到西文字符的,西文字符是不会乱码的

这里讨论一个有趣的问题,就是给你一个二进制文件,你怎么肯定采用正确的解码方式解析这个文件以显示正确的字符,这其实是有一些困难的,让咱们针对几种方式进行讨论,首先是unicode系列,由于其有BOM,能够经过BOM识别utf-16和部分带BOM的utf-8,所以首先是utf-8 without BOM 和gb系列的比较,gb编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,而utf-8在双字节时范围为

C080 到dfbf,实际上这个范围是在gb的编码范围的,若是文件中的全部字符通过gb编码后都是在C080 dfbf这个范围,那么编码格式就是不可肯定的,在中文环境的windows中,若是在文本文件中写入“联通”时,在保存时默认是为gbk(即ansi),当在打开时,由于这两个字通过gbk编码范围都在C080 dfbf,而windows默认彷佛是按unicode编码格式打开,在肯定无BOM后,其按utf-8 without BOM处理,当有数字不在utf-8编码范围时,其就认为此文件编码格式不是utf-8,转而用本地编码格式,在中文环境下就是gbk,注意windows假定文件没有出现错误,这是不严谨的,由于“联通”在utf-8的范围,其就会按照utf-8解码,显然会是乱码,还有会出现四字节的冲突状况,在四字节上,gb两个文字整体范围会是81408140 fefefefe,而utf-8四字节表示范围为f0808080f7bfbfbf,这显然在gb四字节的范围内,所以也可能出现冲突状况,至于三字节,经过分析不会出现冲突状况

相关文章
相关标签/搜索