http://www.aoxiang.org 2006-4-2 10:48:02
Unicode是一种字符编码规
范 。
先从ASCII说起。ASCII
是用来表示英文字符的一种编码规
范,每个ASCII字符占用1个
字节(8bits)
因此,ASCII编码可以表示的
最大字符数是256,其实英文字
符并没有那么多,一般只用前12
8个(最高位为0),其中包括了
控制字符、数字、大小写字母和其
他一些符号
。
而最高位为1的另128个字符被
成为“扩展ASCII”,一般用
来存放英文的制表符、部分音标字
符等等的一些其他符号
这种字符编码规范显然用来处理英
文没有什么问题
。(实际上也可以用来处理法文、
德文等一些其他的西欧字符,但是
不能和英文通用),但是面对中文
、阿拉伯文之类复杂的文字,25
5个字符显然不够用
于是,各个国家纷纷制定了自己的
文字编码规范,其中中文的文字编
码规范叫做“GB2312-80
”,它是和ASCII兼容的一种
编码规范,其实就是利用扩展AS
CII没有真正标准化这一点,把
一个中文字符用两个扩展ASCI
I字符来表示。
但是这个方法有问题,最大的问题
就是,中文文字没有真正属于自己
的编码,因为扩展ASCII码虽
然没有真正的标准化,但是PC里
的ASCII码还是有一个事实标
准的(存放着英文制表符),所以
很多软件利用这些符号来画表格。
这样的软件用到中文系统中,这些
表格符就会被误认作中文字,破坏
版面。而且,统计中英文混合字符
串中的字数,也是比较复杂的,我
们必须判断一个ASCII码是否
扩展,以及它的下一个ASCII
是否扩展,然后才“猜”那可能是
一个中文字
。
总之当时处理中文是很痛苦的。而
更痛苦的是GB2312是国家标
准,台湾当时有一个Big5编码
标准,很多编码和GB是相同的,
所以……,嘿嘿。
这时候,我们就知道,要真正解决
中文问题,不能从扩展ASCII
的角度入手,也不能仅靠中国一家
来解决。而必须有一个全新的编码
系统,这个系统要可以将中文、英
文、法文、德文……等等所有的文
字统一起来考虑,为每个文字都分
配一个单独的编码,这样才不会有
上面那种现象出现。
于是,Unicode诞生了。
Unicode有两套标准,一套
叫UCS-2(Unicode-
16),用2个字节为字符编码,
另一套叫UCS-4(Unico
de-32),用4个字节为字符
编码。
以目前常用的UCS-2为例,它
可以表示的字符数为2^16=6
5535,基本上可以容纳所有的
欧美字符和绝大部分的亚洲字符
。
UTF-8的问题后面会提到 。
在Unicode里,所有的字符
被一视同仁。汉字不再使用“两个
扩展ASCII”,而是使用“1
个Unicode”,注意,现在
的汉字是“一个字符”了,于是,
拆字、统计字数这些问题也就自然
而然的解决了
。
但是,这个世界不是理想的,不可
能在一夜之间所有的系统都使用U
nicode来处理字符,所以U
nicode在诞生之日,就必须
考虑一个严峻的问题:和ASCI
I字符集之间的不兼容问题。
我们知道,ASCII字符是单个
字节的,比如“A”的ASCII
是65。而Unicode是双字
节的,比如“A”的Unicod
e是0065,这就造成了一个非
常大的问题:以前处理ASCII
的那套机制不能被用来处理Uni
code了
。
另一个更加严重的问题是,C语言
使用'/0'作为字符串结尾,而
Unicode里恰恰有很多字符
都有一个字节为0,这样一来,C
语言的字符串函数将无法正常处理
Unicode,除非把世界上所
有用C写的程序以及他们所用的函
数库全部换掉
。
于是,比Unicode更伟大的
东东诞生了,之所以说它更伟大是
因为它让Unicode不再存在
于纸上,而是真实的存在于我们大
家的电脑中。那就是:UTF
。
UTF= UCS Transformation Format UCS转换格式
它是将Unicode编码规则和
计算机的实际编码对应起来的一个
规则。现在流行的UTF有2种:
UTF-8和UTF-16
。
其中UTF-16和上面提到的U
nicode本身的编码规范是一
致的,这里不多说了。而UTF-
8不同,它定义了一种“区间规则
”,这种规则可以和ASCII编
码保持最大程度的兼容
。
UTF-8有点类似于Haffm
an编码,它将Unicode编
码为00000000-0000
007F的字符,用单个字节来表
示;
00000080-000007
FF的字符用两个字节表示
00000800-0000FF
FF的字符用3字节表示
因为目前为止Unicode-1
6规范没有指定FFFF以上的字
符,所以UTF-8最多是使用3
个字节来表示一个字符。但理论上
来说,UTF-8最多需要用6字
节表示一个字符。
在UTF-8里,英文字符仍然跟
ASCII编码一样,因此原先的
函数库可以继续使用。而中文的编
码范围是在0080-07FF之
间,因此是2个字节表示(但这两
个字节和GB编码的两个字节是不
同的),用专门的Unicode
处理类可以对UTF编码进行处理
。
下面说说中文的问题。
由于历史的原因,在Unicod
e之前,一共存在过3套中文编码
标准。
GB2312-80,是中国大陆
使用的国家标准,其中一共编码了
6763个常用简体汉字。Big
5,是台湾使用的编码标准,编码
了台湾使用的繁体汉字,大概有8
千多个。HKSCS,是中国香港
使用的编码标准,字体也是繁体,
但跟Big5有所不同。
这3套编码标准都采用了两个扩展
ASCII的方法,因此,几套编
码互不兼容,而且编码区间也各有
不同
因为其不兼容性,在同一个系统中
同时显示GB和Big5基本上是
不可能的。当时的南极星、Ric
hWin等等软件,在自动识别中
文编码、自动显示正确编码方面都
做了很多努力
。
他们用了怎样的技术我就不得而知
了,我知道好像南极星曾经以同屏
显示繁简中文为卖点。
后来,由于各方面的原因,国际上
又制定了针对中文的统一字符集G
BK和GB18030,其中GB
K已经在Windows、Lin
ux等多种操作系统中被实现。
GBK兼容GB2312,并增加
了大量不常用汉字,还加入了几乎
所有的Big5中的繁体汉字。但
是GBK中的繁体汉字和Big5
中的几乎不兼容。
GB18030相当于是GBK的
超集,比GBK包含的字符更多。
据我所知目前还没有操作系统直接
支持GB18030。
谈谈Unicode编码,简要解
释UCS、UTF、BMP、BO
M等名词
这是一篇程序员写给程序员的趣味
读物。所谓趣味是指可以比较轻松
地了解一些原来不清楚的概念,增
进知识,类似于打RPG游戏的升
级。整理这篇文章的动机是两个问
题:
问题一:
使用Windows记事本的“另
存为”,可以在GBK、Unic
ode、Unicode big
endian和UTF-8这几种
编码方式间相互转换。同样是tx
t文件,Windows是怎样识
别编码方式的呢?
我很早前就发现Unicode、
Unicode big
endian和UTF-8编码的
txt文件的开头会多出几个字节
,分别是FF、FE(Unico
de),FE、FF(Unico
de big
endian),EF、BB、B
F(UTF-8)。但这些标记是
基于什么标准呢?
问题二:
最近在网上看到一个Conver
tUTF.c,实现了UTF-3
2、UTF-16和UTF-8这
三种编码方式的相互转换。对于U
nicode(UCS2)、GB
K、UTF-8这些编码方式,我
原来就了解。但这个程序让我有些
糊涂,想不起来UTF-16和U
CS2有什么关系。
查了查相关资料,总算将这些问题
弄清楚了,顺带也了解了一些Un
icode的细节。写成一篇文章
,送给有过类似疑问的朋友。本文
在写作时尽量做到通俗易懂,但要
求读者知道什么是字节,什么是十
六进制。
0、big endian和little endian
big endian和little
endian是CPU处理多字节
数的不同方式。例如“汉”字的U
nicode编码是6C49。那
么写到文件里时,究竟是将6C写
在前面,还是将49写在前面?如
果将6C写在前面,就是big
endian。还是将49写在前
面,就是little endian。
“endian”这个词出自《格
列佛游记》。小人国的内战就源于
吃鸡蛋时是究竟从大头(Big-
Endian)敲开还是从小头(
Little-Endian)敲
开,由此曾发生过六次叛乱,其中
一个皇帝送了命,另一个丢了王位
。
我们一般将endian翻译成“
字节序”,将big endian和little
endian称作“大尾”和“小
尾”。
1、字符编码、内码,顺带介绍汉
字编码
字符必须编码后才能被计算机处理
。计算机使用的缺省编码方式就是
计算机的内码。早期的计算机使用
7位的ASCII编码,为了处理
汉字,程序员设计了用于简体中文
的GB2312和用于繁体中文的
big5。
GB2312(1980年)一共
收录了7445个字符,包括67
63个汉字和682个其它符号。
汉字区的内码范围高字节从B0-
F7,低字节从A1-FE,占用
的码位是72*94=6768。
其中有5个空位是D7FA-D7
FE。
GB2312支持的汉字太少。1
995年的汉字扩展规范GBK1
.0收录了21886个符号,它
分为汉字区和图形符号区。汉字区
包括21003个字符。2000
年的GB18030是取代GBK
1.0的正式国家标准。该标准收
录了27484个汉字,同时还收
录了藏文、蒙文、维吾尔文等主要
的少数民族文字。现在的PC平台
必须支持GB18030,对嵌入
式产品暂不作要求。所以手机、M
P3一般只支持GB2312。
从ASCII、GB2312、G
BK到GB18030,这些编码
方法是向下兼容的,即同一个字符
在这些方案中总是有相同的编码,
后面的标准支持更多的字符。在这
些编码中,英文和中文可以统一地
处理。区分中文编码的方法是高字
节的最高位不为0。按照程序员的
称呼,GB2312、GBK到G
B18030都属于双字节字符集
(DBCS)。
有的中文Windows的缺省内
码还是GBK,可以通过GB18
030升级包升级到GB1803
0。不过GB18030相对GB
K增加的字符,普通人是很难用到
的,通常我们还是用GBK指代中
文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,
从区位码到内码,需要在高字节和
低字节上分别加上A0。
在DBCS中,GB内码的存储格
式始终是big endian,即高位在前。
GB2312的两个字节的最高位
都是1。但符合这个条件的码位只
有128*128=16384个
。所以GBK和GB18030的
低字节最高位都可能不是1。不过
这不影响DBCS字符流的解析:
在读取DBCS字符流时,只要遇
到高位为1的字节,就可以将下两
个字节作为一个双字节编码,而不
用管低字节的高位是什么。
2、Unicode、UCS和U
TF
前面提到从ASCII、GB23
12、GBK到GB18030的
编码方法是向下兼容的。而Uni
code只与ASCII兼容(更
准确地说,是与ISO-8859
-1兼容),与GB码不兼容。例
如“汉”字的Unicode编码
是6C49,而GB码是BABA
。
Unicode也是一种字符编码
方法,不过它是由国际组织设计,
可以容纳全世界所有语言文字的编
码方案。Unicode的学名是
"Universal
Multiple-Octet Coded Character Set",简称为UCS。UCS
可以看作是"Unicode
Character Set"的缩写。
根据维基百科全书(http:/
/zh.wikipedia.o
rg/wiki/)的记载:历史
上存在两个试图独立设计Unic
ode的组织,即国际标准化组织
(ISO)和一个软件制造商的协
会(unicode.org)。
ISO开发了ISO
10646项目,Unicode
协会开发了Unicode项目。
在1991年前后,双方都认识到
世界不需要两个不兼容的字符集。
于是它们开始合并双方的工作成果
,并为创立一个单一编码表而协同
工作。从Unicode2.0开
始,Unicode项目采用了与
ISO
10646-1相同的字库和字码
。
目前两个项目仍都存在,并独立地
公布各自的标准。Unicode
协会现在的最新版本是2005年
的Unicode
4.1.0。ISO的最新标准是
10646-3:2003。
UCS规定了怎么用多个字节表示
各种文字。怎样传输这些编码,是
由UTF(UCS
Transformation Format)规范规定的,常见
的UTF规范包括UTF-8、U
TF-7、UTF-16。
IETF的RFC2781和RF
C3629以RFC的一贯风格,
清晰、明快又不失严谨地描述了U
TF-16和UTF-8的编码方
法。我总是记不得IETF是In
ternet
Engineering Task
Force的缩写。但IETF负
责维护的RFC是Interne
t上一切规范的基础。
3、UCS-2、UCS-4、B
MP
UCS有两种格式:UCS-2和
UCS-4。顾名思义,UCS-
2就是用两个字节编码,UCS-
4就是用4个字节(实际上只用了
31位,最高位必须为0)编码。
下面让我们做一些简单的数学游戏
:
UCS-2有2^16=6553
6个码位,UCS-4有2^31
=2147483648个码位。
UCS-4根据最高位为0的最高
字节分成2^7=128个gro
up。每个group再根据次高
字节分为256个plane。每
个plane根据第3个字节分为
256行
(rows),每行包含256个
cells。当然同一行的cel
ls只是最后一个字节不同,其余
都相同。
group 0的plane 0被称作Basic Multilingual Plane,
即BMP。或者说UCS-4中,
高两个字节为0的码位被称作BM
P。
将UCS-4的BMP去掉前面的
两个零字节就得到了UCS-2。
在UCS-2的两个字节前加上两
个零字节,就得到了UCS-4的
BMP。而目前的UCS-4规范
中还没有任何字符被分配在BMP
之外。
4、UTF编码
UTF-8就是以8位为单元对U
CS进行编码。从UCS-2到U
TF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编
码是6C49。6C49在080
0-FFFF之间,所以肯定要用
3字节模板了:1110xxxx
10xxxxxx 10xxxxxx。将6C49写
成二进制是:0110 110001 001001,
用这个比特流依次代替模板中的x
,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的
编码是否正确。
UTF-16以16位为单元对U
CS进行编码。对于小于0x10
000的UCS码,UTF-16
编码就等于UCS码对应的16位
无符号整数。对于不小于0x10
000的UCS码,定义了一个算
法。不过由于实际使用的UCS2
,或者UCS4的BMP必然小于
0x10000,所以就目前而言
,可以认为UTF-16和UCS
-2基本相同。但UCS-2只是
一个编码方案,UTF-16却要
用于实际的传输,所以就不得不考
虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没
有字节序的问题。UTF-16以
两个字节为编码单元,在解释一个
UTF-16文本前,首先要弄清
楚每个编码单元的字节序。例如收
到一个“奎”的Unicode编
码是594E,“乙”的Unic
ode编码是4E59。如果我们
收到UTF-16字节流“594
E”,那么这是“奎”还是“乙”
?
Unicode规范中推荐的标记
字节顺序的方法是BOM。BOM
不是“Bill Of
Material”的BOM表,
而是Byte Order Mark。BOM是一个有点小聪
明的想法:
在UCS编码中有一个叫做"ZE
RO WIDTH NO-BREAK
SPACE"的字符,它的编码是
FEFF。而FFFE在UCS中
是不存在的字符,所以不应该出现
在实际传输中。UCS规范建议我
们在传输字节流前,先传输字符"
ZERO
WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就
表明这个字节流是Big-End
ian的;如果收到FFFE,就
表明这个字节流是Little-
Endian的。因此字符"ZE
RO
WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字
节顺序,但可以用BOM来表明编
码方式。字符"ZERO
WIDTH NO-BREAK SPACE"的UTF-8编码是
EF BB
BF(读者可以用我们前面介绍的
编码方法验证一下)。所以如果接
收者收到以EF BB
BF开头的字节流,就知道这是U
TF-8编码了。
Windows就是使用BOM来
标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode"
(http://www.nad
a.kth.se/i18n/u
cs/unicode-iso1
0646-oview.html
)。
我还找了两篇看上去不错的资料,
不过因为我开始的疑问都找到了答
案,所以就没有看:
"Understanding Unicode A general introduction to the Unicode Standard"
(http://scripts
.sil.org/cms/sc
ripts/page.php?
site_id=nrsi&it
em_id=IWS-Chapt
er04a)
"Character set encoding basics Understanding character set encodings
and legacy encodings"
(http://scripts
.sil.org/cms/sc
ripts/page.php?
site_id=nrsi&it
em_id=IWS-Chapt
er03)