近日须要不同的编码,关于上述编码,一直迷迷糊糊,查了些资料,总算大致了解了,
以下全是从网上搜来的:
1. ASCII和Ansi编码
字符内码(charcter code)指的是用来代表字符的内码.读者在输入和存储文档时都要使用内码,内码分为
单字节内码 -- Single-Byte character sets (SBCS),能够支持256个字符编码.
双字节内码 -- Double-Byte character sets)(DBCS),能够支持65000个字符编码.
前者即为ASCII编码,后者相应ANSI.
至于中文简体编码GB2312,实际上它是ANSI的一个代码页936
2. Unicode
如上,ANSI有非常多代码页,使用不同代码页的内码无法在其它代码也正常显示,这就是为什么日文版/繁体中文版游戏无法在中文简体平台直接显示的原因.
Unicode也是一种字符编码方法,只是它是由国际组织设计,能够容纳全世界全部语言文字的编码方案.它是一种2字节编码,能够提供65536个字符,这个数字是不够表示全部的字符的(汉语就有55000多字符),所以,通过一个代理对的机制来实现附加的917,476个字符表示,以达到全部字符都具有唯一编码.
3.Unicode和BigEndianUnicode
这两者仅仅是存储顺序不同,如"A"的unicode编码为65 00
其BigEndianUnicode编码为00 65
4. UTF-8
这是为传输而设计的编码,其系列还有UTF-7和UTF-16
当中UTF-16和Unicode编码大致一样, UTF-8就是以8位为单元对Unicode进行编码。从Unicode到UTF-8的编码方式例如以下:
Unicode编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
比如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次取代模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
ANSI:系统预设的标准文字储存格式。ANSI是American National Standards Institute的缩写。它成立于1918年,是一个自愿性的组织,拥有超过1300个会员,包含全部大型的电脑公司。ANSI专为电脑工业建立标准,它是世界上相当重要的标准。
Unicode:世界上全部主要指令文件的联集,包含商业和个人电脑所使用的公用字集。当採用Unicode格式储存文件时,可使用Unicode控制字符辅助说明语言的文字覆盖范围,如阿拉伯语、希伯来语。用户在“记事本”中输入含有Unicode字符的文字并储存文件时,系统会提示你必须选取“另存为”中的Unicode编码,这些字符才不会被遗失。须要提醒大家的是,部分Windows 2000字型无法显示全部的Unicode字符。假设发现文件里缺少了某些字符,仅仅需将其变更为其他字型就可以。
Unicode big endian:在Big-endian处理器(如苹果Macintosh电脑)上建立的Unicode文件里的文字位元组(存放单位)排列顺序,与在Intel处理器上建立的文件的文字位元组排列顺序相反。最重要的位元组拥有最低的地址,且会先储存文字中较大的一端。为使这类电脑的用户可以存取你的文件,可选择Unicode big-endian格式。
UTF-8:UTF意为通用字集转换格式(Universal Character Set Transformation Format),UTF-8是Unicode的8位元格式。假设使用仅仅能在同类位元组内支持8个位元的重要资料一类的旧式传输媒体,可选择UTF-8格式。
Unicode是一种字符编码规范 。
先从ASCII说起。ASCII是用来表示英文字符的一种编码规范,每一个ASCII字符占用1个字节(8bits)
因此,ASCII编码能够表示的最大字符数是256,事实上英文字符并没有那么多,一般仅仅用前128个(最高位为0),当中包含了控制字符、数字、大写和小写字母和其它一些符号
。
而最高位为1的另128个字符被成为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其它符号
这样的字符编码规范显然用来处理英文没有什么问题
。(实际上也能够用来处理法文、德文等一些其它的西欧字符,可是不能和英文通用),可是面对中文、阿拉伯文之类复杂的文字,255个字符显然不够用
于是,各个国家纷纷制定了自己的文字编码规范,当中中文的文字编码规范叫做“GB2312-80”,它是和ASCII兼容的一种编码规范,事实上就是利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示。
可是这种方法有问题,最大的问题就是,中文文字没有真正属于自己的编码,由于扩展ASCII码尽管没有真正的标准化,可是PC里的ASCII码还是有一个事实标准的(存放着英文制表符),所以非常多软件利用这些符号来画表格。这样的软件用到中文系统中,这些表格符就会被误认作中文字,破坏版面。并且,统计中英文混合字符串中的字数,也是比較复杂的,我们必须推断一个ASCII码是否扩展,以及它的下一个ASCII是否扩展,然后才“猜”那可能是一个中文字
。
总之当时处理中文是非常痛苦的。而更痛苦的是GB2312是国家标准,*当时有一个Big5编码标准,非常多编码和GB是同样的,所以……,嘿嘿。
这时候,我们就知道,要真正解决中文问题,不能从扩展ASCII的角度入手,也不能仅靠中国一家来解决。而必须有一个全新的编码系统,这个系统要能够将中文、英文、法文、德文……等等全部的文字统一起来考虑,为每一个文字都分配一个单独的编码,这样才不会有上面那种现象出现。
于是,Unicode诞生了。
Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,还有一套叫UCS-4(Unicode-32),用4个字节为字符编码。
以眼下经常使用的UCS-2为例,它能够表示的字符数为2^16=65535,基本上能够容纳全部的欧美字符和绝大部分的亚洲字符
。
UTF-8的问题后面会提到 。
在Unicode里,全部的字符被一视同仁。汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,注意,如今的汉字是“一个字符”了,于是,拆字、统计字数这些问题也就自然而然的攻克了
。
可是,这个世界不是理想的,不可能在一夜之间全部的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。
我们知道,ASCII字符是单个字节的,比方“A”的ASCII是65。而Unicode是双字节的,比方“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了
。
还有一个更加严重的问题是,C语言使用'/0'作为字符串结尾,而Unicode里恰恰有非常多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上全部用C写的程序以及他们所用的函数库全部换掉
。
于是,比Unicode更伟大的东东诞生了,之所以说它更伟大是由于它让Unicode不再存在于纸上,而是真实的存在于我们大家的电脑中。那就是:UTF
。
UTF= UCS Transformation Format UCS转换格式
它是将Unicode编码规则和计算机的实际编码相应起来的一个规则。如今流行的UTF有2种:UTF-8和UTF-16
。
当中UTF-16和上面提到的Unicode本身的编码规范是一致的,这里不多说了。而UTF-8不同,它定义了一种“区间规则”,这样的规则能够和ASCII编码保持最大程度的兼容
。
UTF-8有点相似于Haffman编码,它将Unicode编码为00000000-0000007F的字符,用单个字节来表示;
00000080-000007FF的字符用两个字节表示
00000800-0000FFFF的字符用3字节表示
由于眼下为止Unicode-16规范没有指定FFFF以上的字符,所以UTF-8最多是使用3个字节来表示一个字符。但理论上来说,UTF-8最多须要用6字节表示一个字符。
在UTF-8里,英文字符仍然跟ASCII编码一样,因此原先的函数库能够继续使用。而中文的编码范围是在0080-07FF之间,因此是2个字节表示(但这两个字节和GB编码的两个字节是不同的),用专门的Unicode处理类能够对UTF编码进行处理。
以下说说中文的问题。
由于历史的原因,在Unicode之前,一共存在过3套中文编码标准。
GB2312-80,是*使用的国家标准,当中一共编码了6763个经常使用简体汉字。Big5,是*使用的编码标准,编码了*使用的繁体汉字,大概有8千多个。HKSCS,是中国香港使用的编码标准,字体也是繁体,但跟Big5有所不同。
这3套编码标准都採用了两个扩展ASCII的方法,因此,几套编码互不兼容,并且编码区间也各有不同
由于其不兼容性,在同一个系统中同一时候显示GB和Big5基本上是不可能的。当时的南极星、RichWin等等软件,在自己主动识别中文编码、自己主动显示正确编码方面都做了非常多努力
。
他们用了如何的技术我就不得而知了,我知道好像南极星以前以同屏显示繁简中文为卖点。
后来,由于各方面的原因,国际上又制定了针对中文的统一字符集GBK和GB18030,当中GBK已经在Windows、Linux等多种操作系统中被实现。
GBK兼容GB2312,并添加�了大量不经常使用汉字,还添�了差点儿全部的Big5中的繁体汉字。可是GBK中的繁体汉字和Big5中的差点儿不兼容。
GB18030相当于是GBK的超集,比GBK包含的字符很多其它。据我所知眼下还没有操作系统直接支持GB18030。
谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
这是一篇程序猿写给程序猿的趣味读物。所谓趣味是指能够比較轻松地了解一些原来不清楚的概念,增进知识,相似于打RPG游戏的升级。整理这篇文章的动机是两个问题:
问题一:
使用Windows记事本的“另存为”,能够在GBK、Unicode、Unicode big
endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是如何识别编码方式的呢?
我非常早前就发现Unicode、Unicode big
endian和UTF-8编码的txt文件的开头会多出几个字节,各自是FF、FE(Unicode),FE、FF(Unicode big
endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
近期在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过相似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian
big endian和little
endian是CPU处理多字节数的不同方式。比如“汉”字的Unicode编码是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个字符,包含6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。当中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包含21003个字符。2000年的GB18030是代替GBK1.0的正式国家标准。该标准收录了27484个汉字,同一时候还收录了藏文、蒙文、*文等基本的少数民族文字。如今的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般仅仅支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有同样的编码,后面的标准支持很多其它的字符。在这些编码中,英文和中文能够统一地处理。区分中文编码的方法是高字节的最高位不为0。依照程序猿的称呼,GB2312、GBK到GB18030都属于双字节字符集
(DBCS)。
有的中文Windows的缺省内码还是GBK,能够通过GB18030升级包升级到GB18030。只是GB18030相对GBK添加�的字符,普通人是非常难用到的,通常我们还是用GBK指代中文Windows内码。
这里还有一些细节:
GB2312的原文还是区位码,从区位码到内码,须要在高字节和低字节上分别加上A0。
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
GB2312的两个字节的最高位都是1。但符合这个条件的码位仅仅有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。只是这不影响DBCS字符流的解析:在读取DBCS字符流时,仅仅要遇到高位为1的字节,就能够将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode仅仅与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.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(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、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一贯风格,清楚、明快又不失严谨地描写叙述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet
Engineering Task
Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上仅仅用了31位,最高位必须为0)编码。以下让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4依据最高位为0的最高字节分成2^7=128个group。每一个group再依据次高字节分为256个plane。每一个plane依据第3个字节分为256行
(rows),每行包含256个cells。当然同一行的cells仅仅是最后一个字节不同,其余都同样。
group 0的plane 0被称作Basic Multilingual Plane,
即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而眼下的UCS-4规范中还没有不论什么字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式例如以下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
比如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx
10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001,
用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者能够用记事本測试一下我们的编码是否正确。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码相应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。只是由于实际使用的UCS2,或者UCS4的BMP必定小于0x10000,所以就眼下而言,能够觉得UTF-16和UCS-2基本同样。但UCS-2仅仅是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每一个编码单元的字节序。比如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。假设我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of
Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK
SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出如今实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO
WIDTH NO-BREAK SPACE"。
这样假设接收者收到FEFF,就表明这个字节流是Big-Endian的;假设收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO
WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不须要BOM来表明字节顺序,但能够用BOM来表明编码方式。字符"ZERO
WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB
BF(读者能够用我们前面介绍的编码方法验证一下)。所以假设接收者收到以EF BB
BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
系统支持
Windows 98 :仅仅支持ANSI。
Windows 2k :既支持ANSI又支持UNICODE。
Windows CE :仅仅支持UNICODE。
Windows 2000整个OS系统都是基于UNICODE的,为此在windows 2000下使用ANSI是须要付出代价的,尽管在编码上不用不论什么的转换,可是这样的转化是隐藏的,是占用系统资源的(CPU,内存)。
在Windows 98下必须使用UNICODE,则须要自己手动的编码切换。
在计算机中字符通常并非保存为图像,每一个字符都是使用一个编码来表示的,而每一个字符到底使用哪个编码代表,要取决于使用哪个字符集(charset)。
在最初的时候,Internet上仅仅有一种字符集——ANSI的ASCII字符集,它使用7 bits来表示一个字符,总共表示128个字符,当中包含了英文字母、数字、标点符号等经常使用字符。之后,又进行扩展,使用8 bits表示一个字符,能够表示256个字符,主要在原来的7 bits字符集的基础上添�了一些特殊符号比如制表符。
后来,因为各国语言的添�,ASCII已经不能满足信息交流的须要,因此,为了能够表示其他国家的文字,各国在ASCII的基础上制定了自己的字符集,这些从ANSI标准派生的字符集被习惯的统称为ANSI字符集,它们正式的名称应该是MBCS(Multi-Byte Chactacter System,即多字节字符系统)。这些派生字符集的特点是以ASCII 127 bits为基础,兼容ASCII 127,他们使用大于128的编码作为一个Leading Byte,紧跟在Leading Byte后的第二(甚至第三)个字符与Leading Byte一起作为实际的编码。这样的字符集有非常多,我们常见的GB-2312就是当中之中的一个。
比如在GB-2312字符集中,“连通”的编码为C1 AC CD A8,当中C1和CD就是Leading Byte。前127个编码为标准ASCII保留,比如“0”的编码是30H(30H表示十六进制的30)。软件在读取时,假设看到30H,知道它小于128就是标准ASCII,表示“0”,看到C1大于128就知道它后面有一个另外的编码,因此C1 AC一同构成一个整个的编码,在GB-2312字符集中表示“连”。
因为每种语言都制定了自己的字符集,导致最后存在的各种字符集实在太多,在国际交流中要经常转换字符集非常不便。因此,提出了Unicode字符集,它固定使用16 bits(两个字节、一个字)来表示一个字符,共能够表示65536个字符。将世界上差点儿全部语言的经常使用字符收录当中,方便了信息交流。标准的Unicode称为UTF-16。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8,使用相似MBCS的方式对Unicode进行编码。注意UTF-8是编码,它属于Unicode字符集。Unicode字符集有多种编码形式,而ASCII仅仅有一种,大多数MBCS(包含GB-2312)也仅仅有一种。
比如“连通”两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90
而其UTF-8编码为:E8 BF 9E E9 80 9A
最后,当一个软件打开一个文本时,它要做的第一件事是决定这个文本到底是使用哪种字符集的哪种编码保存的。软件有三种途径来决定文本的字符集和编码:
最标准的途径是检測文本最开头的几个字节,例如以下表:
开头字节 Charset/encoding
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.
比如插入标记后,连通”两个字的UTF-16 (big endian)和UTF-8码分别为:
FF FE DE 8F 1A 90
EF BB BF E8 BF 9E E9 80 9A
可是MBCS文本没有这些位于开头的字符集标记,更不幸的是,一些早期的和一些设计不良的软件在保存Unicode文本时不插入这些位于开头的字符集标记。因此,软件不能依赖于这样的途径。这时,软件能够採取一种比較安全的方式来决定字符集及其编码,那就是弹出一个对话框来请示用户,比如将那个“连通”文件拖到MS Word中,Word就会弹出一个对话框。
假设软件不想麻烦用户,或者它不方便向用户请示,那它仅仅能採取自己“猜”的方法,软件能够依据整个文本的特征来推測它可能属于哪个charset,这就非常可能不准了。使用记事本打开那个“连通”文件就属于这样的情况。
我们能够证明这一点:在记事本中键入“连通”后,选择“Save As”,会看到最后一个下拉框中显示有“ANSI”,这时保存。当再当打开“连通”文件出现乱码后,再点击“File”->“Save As”,会看到最后一个下拉框中显示有“UTF-8”,这说明记事本觉得当前打开的这个文本是一个UTF-8编码的文本。而我们刚才保存时是用ANSI字符集保存的。这说明,记事本推測了“连通”文件的字符集,觉得它更像一个UTF-8编码文本。这是因为“连通”两个字的GB-2312编码看起来更像UTF-8编码导致的,这是一个巧合,不是全部文字都这样。能够使用记事本的打开功能,在打开“连通”文件时在最后一个下拉框中选择ANSI,就能正常显示了。反过来,假设之前保存时保存为UTF-8编码,则直接打开也不会出现故障。
假设将“连通”文件放入MS Word中,Word也会觉得它是一个UTF-8编码的文件,但它不能确定,因此会弹出一个对话框询问用户,这时选择“中文简体(GB2312)”,就能正常打开了。记事本在这一点上做得比較简化罢了,这与这个程序的定位是一致的。