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
xxxxxx    -------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的判定类似仅仅要知道编码规则就能够。

上一篇:C语言:Unicode字符集


下一篇:Unicode字符集下CString与char *转换 (解决中文乱码等)(转)