Unicode 字符集与它的编码方式

正式内容开始之前,我们先来了解一个基本概念,编码字符集。

    编码字符集:编码字符集是一个字符集,它为每一个字符分配一个唯一数字。Unicode 标准的核心是一个编码字符集,字母“A”的编码为 004116 和字符“”的编码为 20AC16。Unicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,所以“A”的编码书写为“U+0041”。

 1 ASCII码

    我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从0000000到11111111。

   上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

ASCII码一共规定了128个字符的编码(准确地说ASCII码是一个编码字符集),比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。后128个称为扩展ASCII码,目前许多基于x86的系统都支持使用扩展ASCII码。

   256个ASCII码中的后128个扩展码可定制用来表示特殊字符和非英语字符,GB2312就是利用这后面的128个扩展字符来表示汉字,[161,254]共94个字符来组成双字节来表示简体汉字字符表。

2 Unicode编码字符集

   光是英语字符ASCII编码字符集是够了,但是如果算上世界上其他的语言的字符,ASCII码显然不够了,于是Unicode编码字符集应运而生。

   Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转  换到程序数据的编码方案。

 

3 UTF-8

  http://zh.wikipedia.org/wiki/UTF-8

      Unicode编码字符集只是统一定义了所有字符和它对应Unicode编码值,而我们的程序中怎么去存储和读取这个Unicode编码值呢?显然,你可以直接统一规定所有Unicode编码值用四个字节来存储。但是这样的话,对于Unicode编码字符集中的与ASCII码表对应的那部分字符(只需要一个字节来表示的Unicode编码值)就有点浪费了。这样,utf-8也就粉墨登场了。

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

Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000  ~  0000 007F | 0xxxxxxx               --------7bit
0000 0080  ~  0000 07FF | 110xxxxx 10xxxxxx    -------11bit
0000 0800  ~  0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx -----16bit
0001 0000  ~  0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ----21bit

UTF-8编码规则:

1> 对于Unicode编码值的二进制位数小于等于7的情况,用一个字节来表示这个Unicode编码值,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2> 对于Unicode编码值的二进制位数大于等于8并且小于等于11的情况,用两个字节来表示,第一个字节的前位都设为1,第+1位设为0,第二个字节的前两位设为10。剩下的没有提及的二进制位,全部填充这个符号对应的unicode码。

3>  以此类推,对于需要n个utf-8 字节来表示的的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部填充这个符号对应的unicode码。

 utf-8编码长度最大为四个字节,所以最多只能表示Unicode编码值的二进制数为21位的Unicode字符

4. UTF-16   

   http://zh.wikipedia.org/wiki/UTF-16  

16进制编码范围 UTF-16表示方法(二进制) 10进制码范围 字节数量
U+0000---U+FFFF xxxxxxxx xxxxxxxx 0-65535 2
U+10000---U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111 4

UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2字节)储存----0号平面(包含所有基本的字符)都在此表示范围,但UTF-16却无法相容于ASCII编码.

 

   UTF-16编码以16位无符号整数为单位。我们把Unicode 编码记作U。编码规则如下:

  如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。

  如果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

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

 

5. 标准Unicode编码表分析

Unicode字符平面映射:

http://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%B9%B3%E9%9D%A2%E6%98%A0%E5%B0%84

完整的Unicode编码表可见链接:http://zh.wikibooks.org/wiki/Unicode 

目前的 Unicode 字符分为 17 组编排, 每组称为平面(Plane),而每平面拥有65536(即 216)个代码点。然而目前只有少数平面被使用。
上述的Unicode编码表链接中只列出了少数几个已经被使用的平面。

U+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
0010 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
0020 SP ! " # $ % & ' ( ) * + , - . /
0030 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0040 @ A B C D E F G H I J K L M N O
0050 P Q R S T U V W X Y Z [ \ ] ^ _

表分为横竖两列,相当于x和y确定唯一的Unicode的值(十六进制)。如:
ESC字符由x座标0010和y座标B确定,那么它的Unicode编码值就是0010 + B =  001B
表就是这么读的啦。

6. UTF-8和UTF-16字节序的问题

  http://zh.wikipedia.org/wiki/UTF-8

  http://zh.wikipedia.org/wiki/UTF-16

 网上查了下,对于这二者的字节序的原因很表面。字节序的介绍如下:
    UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,所以有字节序的问题。
    那为什么以字节为编码单元的utf-8就没有字节序的问题呢?它也可以有三个或四个字节组成呀。我想很多人都会这么问的,也包括我自己。让我们再回头来看看字节序是什么东西,对於单个字节长度的变量类型来说,它是没有字节问题的。对于多字节长度的变量类型才有字节问题。

   如果牵涉到在多个CPU之间传输数字问题.如通过网络,文件和总线传输数字的话.必须要考虑到数字字节序问题。网络传输时,不同字节序的平台进行数据传输时,在发送数据之前都必须转换成为网络字节序后再进行传输,即统一用的网络字节序(即大头)来传输。接收方根据自己cpu的字节序将网络字节序转换为本地字节序,这样一来,接收方按再按照自己的字节序去读取这个数据时和发送方读取的是一样的。因此,对于网络传输,网络传输这一层面的字节序问题已经解决,Unicode编码层面的字节序问题与下面的文件存储中的一样。总线,这个是同一平台多cpu(各cpu的字节序不一样)之间的数据交换的情况,我想我们基本上是接触不到这种情况了,不做研究之列。剩下就是个通过文件传输数据的情况,即一个平台上的文件在另外一个不同字节序的平台上打开。这种情况怎么考虑字节序的问题呢?

   再回过头来仔细分析"UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,所以有字节序的问题。"这句话的含义了。什么是编码单元?Unicode只是一个符号集,根据UTF-8或是UTF-16可以计算出某个符号对应的的唯一的二进制代码,但是这个二进制的代码具体怎么存储的呢,这个编码规则中都没有指定。但是UTF-8指定了,存储这个二进制代码时,必须按字节为单元来读和存储;UTF-16指定了,存储这个二进制代码时,必须按两个字节为单元来读和存储。这个时候我们应该知道其实编码单元,就是某种编码方式中,最小读取和存储其二进制数值的单元。呵呵,明白这个很重要哦。

   无论是UTF-8或UTF-16,其最终的二进制编码长度都有可能是大于等于两个字节的。但是UTF-8是按照字节为编码单元,所以存储时是按照其编码顺序(二进制数值从左到右)的顺序来存储的。所以读取文件时,按照文件中存储顺序一个字节一个字节读取就能得到和存储时一样的二进制编码了,也就不需要考虑字节序了。而UTF-16是按照两个字节为编码单元的,它的二进制编码只有两个字节或四个字节这两种长度。所以存储时,按照编码顺序(二进制数值从左到右),一次存储两个字节到文件,直到全部存完。这样两个字节与其下一个两个字节之间的存储顺序也是与二进制编码顺序一致的。但是作为编码单元的这两个字节本身的顺序怎么放呢?UTF-16是没有指定的,你可以根据自己的喜好人为地按大头来存放这两个字节,也可以按照小头来存放。所以你按照UTF-16编码后存储的文件,别人去读你这个文件中的内容时,是按照大头还是小头去读这个编码单元呢?

  如果还有那么一点晕的话,我再举个例子了:
假设要将 U+64321 (16进位) 转成 UTF-16 编码. 因为它超过 U+FFFF, 所以他必须编译成32位(4个byte)的格式,如下所示:11
V  = 0x64321
Vx = V - 0x10000
   = 0x54321
   = 0101 0100 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 二进制编码应该是:
1101 1001 0101 00001101 1111 0010 0001
绿色部分为高16位,红色为低16位。
因为UTF-16 的编码单元为两个字节,所以存储时,按照二进制编码顺序,先存储高位的两个字节1101 10010101 0000 ,然后再存储地位的两个字节1101 1111 0010 0001。
但是两个字节之间的顺序是什么呢?顺序就是先存储两个字节中的"低地址"的那个字节,然后再存储高地址的那个字节。

如果是小头顺序(最低字节在最低位,最高字节在最高位),则"低地址"中存储的是低字节。所以,在高位的两个字节中,先存储最低位的字节50,再存储最高位的字节D9,即十六进制格式存储为50D9 ;同理,低位的两个字节的存储顺序就是21DF。四个字节连起来,存储内容就是50D9 21DF。
如果是大头顺序(最高字节在地址最低位,最低字节在地址最高位),则"低地址"中存储的是高字节。所以,高位连个字节先存储最低位的字节D9,再存储最高为的字节50 ----D950
同理,低位的两个字节的存储顺序就是DF21。四个字节连起来,存储内容就是D950 DF21。
 

 7. UTF-8和UTF-16字节序的问题的对应解决方案   
   经过上述的分析,相信大家应该清楚了字节序的问题了。那么如何让别的程序读取你写的文件时知道你存储时的字节序呢?
   Unicode规范中定义,每一个文件内容的最前面加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),这个字符对应的Unicode字符的编码值为FEFF。所以对于UTF-16,如果你写的时候使用小头顺序,则这个字符在文件中的存储顺序为FFFE;如果是大头顺序,则这个字符的存储顺序为FEFF。
   UTF -8本来是与字节序无关的,不需要指定编码字节序,但是可以用BOM(Byte Order Mark)来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF,因为是以字节为编码单元存储的,所以这个字符文件中对应的存储格式与编码格式一样。根据这一点,我们知道如果文件内容以EF BB BF开头,就知道这是UTF-8编码了。
 8. 不同编码方式的转换
   windows下,记事本中,支持将文件中的内容以不同的编码方式存储。
   ANSI是记事本中默认的编码方式-----对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
  记事本也支持utf-8格式的,通过依次采用ANSI 和 UTF-8编码方式保存,我们可以看到这两种编码方式之间的转换。用文本编辑软件UltraEdit中的十六进制格式,观察该文件的不同编码方式对应的值。

9 读写文本文件时,程序时如何判断文件的编码格式

 1. 首先,程序通过通过判断文件头几个字节来判断文件的编码格式(BOM字节)

ANSI :         无格式定义;
Unicode :      前两个字节为 FFFE ;
Unicode big endian : 前两字节为 FEFF ; 
UTF-8 :        前两字节为 EFBB ;

UTF-16 big endian的BOM: FF FE;   
UTF-16 little endian的BOM: FE FF;

2. BOM不存在的情况判定。

    UTF-8的判定,根据内容判定

        UTF-8的编码规则:

                   字符字节长度        标志字节        值

                     一字节长            0XXXXXXX

                     两字节长            110XXXXX  10XXXXXX

                     三字节长            1110XXXX  10XXXXXX 10XXXXXX 

                     四字节长            11110XXX  10XXXXXX 10XXXXXX 10XXXXXX 

        **标志字节判定用到的数据**

        定义数组btHead 长度为4保存用来判定标志字节的十进制数值: 0,192, 224, 240

        定义数组btBitAndValue 长度为4保存用来得到标志字节长度的十进制数值:128: 224, 240, 248

        **值判定用到的数据**

        定义变量btValueHead用来保存值得标志所对应的十进制数值:128

        定义变量btFixValueAnd 保存用来取得值的标志的十进制数值:192

 

        a.以字节方式读取文件中内容保存到字节数组中

        b.对a中读取的文件内容做loop操作。

           首先对当前的字节分别与btBitAndValue中的四个值进行位与操作,每次得到的值与btHead中的值比较,找到相等的值时可以根据当前的   值来判定字符的字节长度L。并执行下一个循环,在跳过b的操作次数(L - 1)次时在执行b操作

        c.取得值的标志。 将此致的值与btFixValueAnd进行位与操作,将取得的值与btValueHead进行比较,如果相等则对下一个字节继续执行c操作,直到执行的次数是L-1次。如果不相等则说明不是UTF-8编码格式。

   UTF-16的判定与UTF-8的判定类似只要知道编码规则就可以。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章