计算机字符编码及Unicode简介

计算机,毫无疑问是一部机器,在最初我们接触计算机时或者接收计算机教育时,我们就知道:计算机能识别的只有0和1组成的二进制编码。人与计算机交互早期也是用二进制编码方式,当时人们或通过扳动计算机庞大的面板上无数的开关来向计算机输入信息(比如开代表1,关代表0),或使用打孔卡片来向计算机输入指令和数据(比如有孔代表1,无孔代表0)。
终端(比如显示器)和键盘组成的字符人机界面的诞生让人们大大提高了与计算机的交互效率。这里提到了'字符',那么什么是'字符'?说的通俗些:字符就是人们使用的记号,抽象意义上的一个符号。比如阿拉伯数字1,这就是一个符号,这个符号的抽象含义:1代表一种数量的概念。
人类的记号五花八门,包括国家文字、标点符号、图形符号、数字等。这些在计算机领域会被统称为'字符'。而所有字符的集合就被称为'字符集'。有了'字符'概念,那么在计算机中如何表示'字符'呢?前文提到了计算机中都是用二进制bit来交流的,'字符'也只能建筑在 0或1两种bit的基础上。这里用英文字母表来举例说明。如何用两种状态来表示26种字符呢?是的,最容易想到的方案就是通过多位组合来表示26个字符中的1个。那么,多少bit表示一个字母合适呢?或者说我们的字符集有多大呢?如果字符集里只有8个字符,那么我用3个bit的组合就可以将这些字符都表示和识别出来。26个字母最多用5个二进制位就可以表示(5位共有2^5=32种状态)。想当年美国人也在考虑这个问题,不过美国人想当然的就认为:所有能用到的有现实意义的字符不超过256个,当时美国人也只用到了128个, 预留128个备用,而256个字符的字符集用8bit就可以表示,这就是举世闻名的美国标准信息交换代码( American Standard Code for Information Interchange, ASCII)。而这8bit恰与计算机中的基本存储数据单元-'字节'的位个数相同,这样一个字节就恰可以表示一个ASCII字符了。如:ASCII字符 'A'的内存位模式:二进制:01000001,十进制:65,十六进制:0x41。

这里提到了一个'编码'的概念,上面提到的ASCII就是众多字符编码规范中的一种,最早的一种,最重要的一种。那么什么是字符编码呢?回顾一下ASCII在制订的时候都做了哪些事:
1) 规定用8bit即一个字节来表示一个ASCII字符;
2) 制定了ASCII字符表,即该字符集中的每个字符对应的位模式。如:ASCII字符'B'的内存位模式:0x42,'1'的内存位模式:0x31。

由此看来一个字符编码规范要做两件事:
1) 规定这个字符集中的字符用多少字节来表示;
2) 制订该字符编码集的字符表,即该字符集中每个字符对应的位模式
1)和2)这两个规定合在一起就是编码。

随着计算机的普及,世界各国都开始使用计算机,但是对于非英语国家如中、日、韩等来说,ASCII码是远远不能满足本国人的需要的,我国的汉字有几万个,是不可能用这256个字符(精确的说是128个字符)所能表达出来的。我们也要制定自己的编码,同样日本人、韩国人也都是这么做的。这样一来,世界范围内就多了诸如GB2312、BIG5、JIS等局限于某个国家或地区使用的本地化编码标准,这些编码标准被统称为:ANSI编码。这些 ANSI编码有一些共同的特点:
1) 每种ANSI编码或者说ANSI字符集只规定自己国家或地区使用的语言所需的'字符';比如中文GB-2312编码中就不会包含韩国人的文字。
2) ANSI字符集的空间都比ASCII要大很多,一个字节已经不够,绝大多数都使用了多字节的存储方案。
3) ANSI编码一般都会兼容ASCII码。

ANSI 的出现让计算机迅速普及到世界的每个角落,每个国家都利用上了这样的先进的工具提高了自己的生产力。打开Windows记事本,"另存为"对话框的"编码 "下拉框中有ANSI编码,在简体中文系统下,ANSI编码代表GB2312编码,在日文操作系统下,ANSI 编码代表 JIS 编码。但是随着互联网的兴起,问题出现了。由于ANSI码的第一个特点:各个国家或地区在编制自己的ANSI码时并未考虑到其他国家或地区的ANSI码, 导致编码空间有重叠,比如:汉字'中'的GB编码是[0xD6,0xD0],这个编码在JIS中是什么呢,我不知道,我也不愿意去查那些稀奇古怪的鬼子文,但我可以肯定的是肯定不是'中'这个字符了。这样一来当在不同ANSI编码系统之间进行信息交换和展示的时候,乱码就不可避免了。

为了使国际间信息交流更加方便,Unicode字符集编码诞生。Unicode是Universal Multiple-Octet Coded Character Set的缩写,中文含义是"通用多八位编码字符集"。它是由一个名为 Unicode学术学会(Unicode Consortium)的机构制订的字符编码系统,Unicode目标是将世界上绝大多数国家和地区的文字、符号都编入其字符集,它为每种语言中的每个字符设定了统一并且唯一的二进制编码(位模式),以满足跨语言、跨平台进行文本转换、处理的要求,以达到支持现今世界各种不同语言的书面文本的交换、处理及显示的目的,使世界范围人们通过计算机进行信息交换时达到畅通自如而无障碍。说白了Unicode编码就是先将世界上存在的绝大多数常用字符纳入 Unicode字符集,然后进行统一排号。而每个Unicode字符的编码(位模式)就是该字符在Unicode字符表中的序号,所以与上面提到的 ANSI编码不同的是,一个Unicode字符的编码用的是一个整数表示,而这个整数的长度通常>= 2个字节。这样Unicode编码在不同平台存储时就要注意其字节序了。比如:采用标准Unicode编码的'中'在Windows上的存储就是 '2D4E',而在SPARC Solaris上的存储则是'4E2D'。

上面提到了标准Unicode编码,难道还有其他 Unicode编码方式,的确,Unicode的出现的确使我们在统一计算机编码过程中迈出的一大步,但是毕竟Unicode诞生才10几年,这之前大家 一直使用ASCII码,一直使用各自的ANSI编码。要想一次性将全世界的计算机系统都统一改为Unicode编码,可能性不大。那么现在越来越多的新系统都开始支持并使用Unicode,这些新系统与旧系统之间如何交换数据其实是首要难题。于是一个新名词又诞生了,那就是UTF, Unicode Translation Format,即把Unicode转做某种格式的意思。为什么要转换成某种格式呢?转换是为了传输和交换。一种好的UTF-x方案应该便于在不同的计算机 之间使用网络传输不同语言和编码的文字,使得标准双字节的Unicode能够在现存的处理单字节的系统上正确传输。目前比较常见的UTF方案有三种:
UTF-16:其本身就是标准的Unicode编码方案,又称为UCS-2,它固定使用16 bits(两个字节)整数来表示一个字符。
UTF-32:又称为UCS-4,它固定使用32 bits(四个字节)整数来表示一个字符。
UTF -8:最广泛的使用的UTF方案,UTF-8使用可变长度字节来储存Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西 里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。UTF-8更便于在使用Unicode的系统与现存的单字节的系统 进行数据传输和交换。与前两个方案不同:UTF-8以字节为编码单元,没有字节序的问题。

UTF有三种方案,那么如何在接收数据和存储数 据时识别数据和指导识别数据采用的是哪个方案呢?在UTF编码方案中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输或存储中。UCS规范建议我们在传输或存储字节 流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。这样根据识别前面的"ZERO WIDTH NO-BREAK SPACE"即可识别编码方案:
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.

以上是简略的字符编码的基本知识。下面将编码与具体的编程语言结合起来进行更直观的学习。这里还是以C语言举例。
C 语言定义了两个字符集(character set):源代码字符集(source character set)是用于组成C源代码的字符集合,而运行字符集(execution character set)是可以被执行程序解释的字符集合。应用程序都有自己的执行字符集,也就说在应用程序执行过程中使用什么字符集或字符编码来识别各种数据存储介质中 的bit流。

[Example1]

int main() {
wchar_t ws[] = L"中文"; --- (1)
wprintf(L"%s\n", ws);
}

编译该程序gcc编译器提示:(1)这行:converting to execution character set: Illegal byte sequence
为什么转换失败呢?我们看到程序中使用了宽字符常量。这里先插入一段C语言的小故事:多字节字符和宽字节字符。
C 语言原本是在英文环境中设计的,主要的字符集是ASCII字符。但是国际化软件必须能够表示不同的字符,而这些字符数量庞大,无法使用一个字节编码,于是 在1994年,"Normative Addendum 1"(基准增补一)的采用,让ISO C可以标准化两种表示大型字符集的方法:宽字符(wide character,该字符集内每个字符使用相同的位长)以及多字节字符(multibyte character,每个字符可以是一到多个字节不等,而某个字节序列的字符值由字符串或流(stream)所在的环境背景决定)。自从1994 年的增补之后,C不只提供char类型,还提供wchar_t类型(宽字符)。虽然此次C标准仍没有支持Unicode字符集,但许多实现版本使用 Unicode转换格式UTF-16和UTF-32来处理宽字符(我遇到的mingw gcc用的是UTF-16, Sun Sparc Gcc用的则是UTF-32),也就是说在大部分标准C实现版本中,默认的一个wchar_t就是一个unicode字符,一个宽字符实际上就是一个 unicode字符,一个宽字符常量字符串(L"...")实际上是一个unicode编码的常量字符串。这样我们来解释上面的问题。

上 面程序中编译器在遇到宽字符常量:L"中文"时,试图将之转换成unicode码存储,mingw gcc试图使用默认的源代码符号集->unicode的转码方式转换"中文"这个字面量的二进制位模式到unicode位模式,但却发现"中文"这 个字面量的位模式不能识别,这就需要我们在外部告知gcc我们的这个"中文"字面量的位模式是GB2312的,我们使用:gcc -finput-charset=GB2312 testwprintf.c就能解决这一问题了。

好了,编译完了。我们来执行一下 a.exe,但却发现在控制台没有任何输出,又出现什么问题了呢?分析一下:目前我们的ws中使用的位模式是unicode编码位模式,哇,原来 wprintf并不支持直接输出:unicode编码。类似:printf, wprintf等输出到控制台或者文件的库函数只支持ANSI编码或多字节编码输出。其实这是符合C语言规范的,因为C标准并未支持Unicode,只是 很多C的实现将宽字符用unicode的位模式表示罢了。这时我们需要通过setlocale函数来设置如何将unicode编码的宽字符转换成一种可以 输出的编码。
[Example2]

int main() {
wchar_t ws[] = L"中文";
setlocale(LC_ALL, "chs");
wprintf(L"%s\n", ws);
}
setlocale(...)只在运行时起作用,这样编译执行后,"中文"二字就会显示在我们的控制台上了。

当然了我们还可以通过标准库调用将宽字符手动转成ANSI字符后再直接输出。
[Example3]

int main() {
wchar_t ws[] = L"中文"; 
char ms[12];
memset(&ms, 0, sizeof(ms));

setlocale(LC_ALL, "chs");
wcstombs(ms, ws, sizeof(ms));
printf("%s\n", ms);
}
编译执行后,"中文"二字同样跃然纸上。wcstombs是将宽字符串按照setlocale设置的编码转成指定的ANSI编码字符串;而mbstowcs 则是按照etlocale设置的编码将将多字节字符串转换成unicode编码存储在宽字符串中。前者调用setlocale是指导目标编码的;后者调用 setlocale的作用是指导如何将源字符串翻译成目的unicode字符串的。类似的还有字符级别的标准函数:wctomb和mbtowc。

关于字符编码转换,其实有很多好用的开源工具包可用,比如著名的iconv,自己平时很少会去实现一个编码转换。学习以上知识只是为了让自己再遇到乱码问题的时候不再迷糊,而且对计算机字符编码知识有一个概念上的了解是必要的且大有裨益的。

 

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有两套标准,一套叫Unicode-16UCS-2,用2个字节为字符编码,另一套叫Unicode-32UCS-4,用4个字节为字符编码。以目前常用的Unicode-16UCS-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。

参考http://www.vckbase.com/document/viewdoc/?id=1733

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值