Unicode 字符集与它的编码方式

摘要:
Unicode标准的核心是编码字符集。字母“A”的代码为004116,字符“”的代码是20AC16.2 Unicode编码字符集仅适用于英文字符和ASCII编码字符集。然而,如果将世界上其他语言的字符包括在内,ASCII码显然是不够的,因此Unicode编码字符集应运而生。显然,您可以直接统一地指定所有Unicode编码值都存储在四个字节中。然而,在这种情况下,与Unicode编码字符集中的ASCII代码表相对应的字符有些浪费。utf-8的最大编码长度为四个字节,因此最多只能表示21位Unicode编码值的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 | 110xxxxx10xxxxxx -------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+FFFFxxxxxxxx xxxxxxxx0-655352
U+10000---U+10FFFF110110yyyyyyyyyy 110111xxxxxxxxxx65536-11141114

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+0123456789ABCDEF
0000NULSOHSTXETXEOTENQACKBELBSHTLFVTFFCRSOSI
0010DLEDC1DC2DC3DC4NAKSYNETBCANEMSUBESCFSGSRSUS
0020SP!"#$%&'()*+,-./
00300123456789:;<=>?
0040@ABCDEFGHIJKLMNO
0050PQRSTUVWXYZ[]^_

表分为横竖两列,相当于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的判定类似仅仅要知道编码规则就能够。


免责声明:文章转载自《Unicode 字符集与它的编码方式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇树莓派ZeroW串口通讯[RoCE]RDMA over Converged Ethernet模式以及配置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Java转码工具native2ascii

背景:在做Java开发的时候,常常会出现一些乱码,或者无法正确识别或读取的文件,比如常见的validator验证用的消息资源(properties)文件就需要进行Unicode重新编码。原因是java默认的编码方式为Unicode,而我们的计算机系统编码常常是GBK等编码。需要将系统的编码转换为java正确识别的编码问题就解决了。 1、native2asc...

python学习--练习题111213--分阶段判断问题、输出指定日期格式问题、统计字符问题

练习11 #利用条件运算符的嵌套来完成此题:学习成绩>=90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示。 学习成绩=float(input("请输入你的学习成绩:")) def 分数判定(): if 学习成绩 <60: print("你的学习成绩在60以下,判定为C") elif 学习成绩...

Firefox浏览器设置字符编码格式

今天写博客,敲示例代码时遇到的问题。简单的HTML页面,在火狐浏览器出现中文乱码。如果是IE浏览器的话,直接通过菜单查看 –> 编码 就可以指定编码方式,解决乱码问题。现在Firefox浏览器版本升级太快,且展示方式也经常变化,本人还找了一番才找到火狐浏览器的设置字符编码的方式,在此分享出来,希望能帮到碰到这个问题的朋友。 本人使用的Firefo...

c语言-格式控制字符 %XXd 用法

d格式字符 用来输出十进制整数,有以下几种用法: 1. %d, 按整型数据的实际长度输出。 2. %md,m为指定输出的整型位数的宽度,如果整型数据的实际位数小于m,则左端补以空格,如果大于m,则按实际位数输出。 3. %0md,同上,当整型数据实际位数小于m时,左端补以数字0,而不是空格。 4. %.md, 同%0md。 5. %ld, 按长整型数据输出...

C Primer Plus(十五)

第十五章 位操作 15.1 二进制数、位和字节 以2为基数表示的数字称为二进制数,可以使用二进制数将任何整数表示为1和0的一个组合,这种系统非常适合于数字计算机使用。 15.1.1 二进制整数 描述存储器芯片和数据传输率时使用的字节指8位字节。最小的二进制数是00000000,或一个简单的0.一个字节可以存储的数的范围是0到255.通过改变对位模式的解释方式...

音频编码器

16 音频编码器 介绍当前可用的音频编码器 aac AAC(Advanced Audio Coding )编码器 当前原生(内置)编码器还处于实验阶段,而且只能支持AAC-LC(低复杂度AAC)。要使用这个编码器,必须选择 ‘experimental’或者'lower' 因为当前还处于实验期,所以很多意外可能发生。如果需要一个更稳定的AAC编码器,参考li...