如果你用放大镜看一下,可以看出屏幕上的字是由一个一个的像素点组成的,每一个字符用一组像素点拼接出来,这些像素点组成一幅图像,变成了我们的文字,计算机又是如何将我们的文字保存起来的呢?是用一个个的点组成的图像将文字保存起来的吗?当然不是,让我们从英文开始,由于英文是拼音文字,实际上所有的英文字符和符号加起来也不超过100个,在我们的文字中存在着如此大量的重复符号,这就意味着保存每个字符的图像会有大量的重复,比如e就是出现最多的符号等等。所以在计算机中,实际上不会保存字符的图像。
什么是字符编码?
由于我们的文字中存在着大量的重复字符,而计算机天生就是用来处理数字的,为了减少我们需要保存的信息量,我们可以使用一个数字编码来表示每一个字符,通过对每一个字符规定一个唯一的数字代号,然后,对应每一个代号,建立其相对应的图形,这样,在每一个文件中,我们只需要保存每一个字符的编码就相当于保存了文字,在需要显示出来的时候,先取得保存起来的编码,然后通过编码表,我们可以查到字符对应的图形,然后将这个图形显示出来,这样我们就可以看到文字了,这些用来规定每一个字符所使用的代码的表格,就称为编码表。编码就是对我们日常使用字符的一种数字编号。
第一个编码表ASCII
在最初的时候,美国人制定了第一张编码表《美国标准信息交换码》,简称ASCII,它总共规定了128个符号所对应的数字代号,使用了7位二进制的位来表示这些数字。其中包含了英文的大小写字母、数字、标点符号等常用的字符,数字代号从0至127,ASCII的表示内容如下:
0–31控制符号
32空格
33-47常用符号
48-57数字
58-64符号
65-90大写字母
91-96符号
97-127小写字母
注意,32表示空格,虽然我们再纸上写字时,只要手腕动一下,就可以流出一个空格,但是,在计算机上,空格与普通得字符一样也需要用一个编码来表示,33-127共95个编码用来表示符号,数字和英文的大写和小写字母。比如数字1所对应的数字代号为49,大写字母A对应的代号为65,小写字母a对应的代号为97。所以,我们所写的代码hello,world保存在文件中时,实际上是保存了一组数字1041011081081114432119111114108100。我们再程序中比较英文字符串的大小时,实际上也是比较字符对应的ASCII的编码大小。
由于ASCII出现最早,因此各种编码实际上都受到了它的影响,并尽量与其相兼容。
扩展ASCII编码ISO8859
美国人顺利解决了字符的问题,可是欧洲的各个国家还没有,比如法语中就有许多英语中没有的字符,因此ASCII不能帮助欧洲人解决编码问题。
为了解决这个问题,人们借鉴ASCII的设计思想,创造了许多使用8位二进制数来表示字符的扩充字符集,这样我们就可以使用256种数字代号了,表示更多的字符了。在这些字符集中,从0-127的代码与ASCII保持兼容,从128到255用于其它的字符和符号,由于有很多的语言,有着各自不同的字符,于是人们为不同的语言制定了大量不同的编码表,在这些码表中,从128-255表示各自不同的字符,其中,国际标准化组织的ISO8859标准得到了广泛的使用。
在ISO8859的编码表中,编号0–127与ASCII保持兼容,编号128–159共32个编码保留给扩充定义的32个扩充控制码,160为空格,161-255的95个数字用于新增加的字符代码。编码的布局与ASCII的设计思想如出一辙,由于在一张码表中只能增加95种字符的代码,所以ISO8859实际上不是一张码表,而是一系列标准,包括14个字符码表。例如,西欧的常用字符就包含在ISO8859-1字符表中。在ISO8859-7种则包含了ASCII和现代希腊语字符。
问题出现了!
ISO的 8859标准解决了大量的字符编码问题,但也带来了新的问题,比如说,没有办法在一篇文章中同时使用ISO8859-1和ISO8859-7,也就是说,在同一篇文章中不能同时出现希腊文和法文,因为他们的编码范围是重合的。例如:在ISO8859-1中217号编码表示字符Ù,而在ISO8859-7中则表示希腊字符Ω,这样一篇使用ISO8859-1保存的文件,在使用ISO8859-7编码的计算机上打开时,将看到错误的内容。为了同时处理一种以上的文字,甚至还出现了一些同时包含原来不属于同一张码表的字符的新码表。
大字符集的烦恼
不管如何,欧洲的拼音文字都还可以用一个字节来保存,一个字节由8个二进制的位组成,用来表示无符号的整数的话,范围正好是0–255。
但是,更严重的问题出现在东方,中国,朝鲜和日本的文字包含大量的符号。例如,中国的文字不是拼音文字,汉字的个数有数万之多,远远超过区区256个字符,因此ISO的8859标准实际上不能处理中文的字符。
通过借鉴ISO8859的编码思想,中国的专家灵巧的解决了中文的编码问题。
既然一个字节的256种字符不能表示中文,那么,我们就使用两个字节来表示一个中文,在每个字符的256种可能中,低于128的为了与ASCII保持兼容,我们不使用,借鉴ISO8859的设计方案,只使用从160以后的96个数字,两个字节分成高位和低位,高位的取值范围从176-247共72个,低位从161–254共94这样,两个字节就有72*94=6768种可能,也就是可以表示6768种汉字,这个标准我们称为GB2312-80。
6768个汉字显然不能表示全部的汉字,但是这个标准是在1980年制定的,那时候,计算机的处理能力,存储能力都还很有限,所以在制定这个标准的时候,实际上只包含了常用的汉字,这些汉字是通过对日常生活中的报纸,电视,电影等使用的汉字进行统计得出的,大概占常用汉字的99%。因此,我们时常会碰到一些名字中的特殊汉字无法输入到计算机中的问题,就是由于这些生僻的汉字不在GB2312的常用汉字之中的缘故。
由于GB2312规定的字符编码实际上与ISO8859是冲突的,所以,当我们在中文环境下看一些西文的文章,使用一些西文的软件的时候,时常就会发现许多古怪的汉字出现在屏幕上,实际上就是因为西文中使用了与汉字编码冲突的字符,被我们的系统生硬的翻译成中文造成的。
不过,GB2312统一了中文字符编码的使用,我们现在所使用的各种电子产品实际上都是基于GB2312来处理中文的。
GB2312-80仅收汉字6763个,这大大少于现有汉字,随着时间推移及汉字文化的不断延伸推广,有些原来很少用的字,现在变成了常用字,例如:朱镕基的“镕”字,未收入 GB2312-80,现在大陆的报业出刊只得使用(金+容)、(金容)、(左金右容)等来表示,形式不一而同,这使得表示、存储、输入、处理都非常不方便,而且这种表示没有统一标准。
为了解决这些问题,全国信息技术化技术委员会于1995年12月1日《汉字内码扩展规范》。GBK向下与GB2312完全兼容,向上支持ISO10646 国际标准,在前者向后者过渡过程中起到的承上启下的作用。GBK亦采用双字节表示,总体编码范围为8140-FEFE之间,高字节在81-FE之间,低字节在40-FE之间,不包括7F。在GBK1.0中共收录了21886个符号,汉字有21003个。
GBK共收入21886个汉字和图形符号,包括:
*GB2312中的全部汉字、非汉字符号。
*BIG5中的全部汉字。
*与ISO10646相应的国家标准GB13000中的其它CJK汉字,以上合计20902个汉字。
*其它汉字、部首、符号,共计984个。
微软公司自Windows95简体中文版开始支持GBK代码,但目前的许多软件都不能很好地支持GBK汉字。
GBK编码区分三部分:
*汉字区包括
GBK/2:OXBOA1-F7FE,收录GB2312汉字6763个,按原序排列;
GBK/3:OX8140-AOFE,收录CJK汉字6080个;
GBK/4:OXAA40-FEAO,收录CJK汉字和增补的汉字8160个。
*图形符号区包括
GBK/1:OXA1A1-A9FE,除GB2312的符号外,还增补了其它符号
GBK/5:OXA840-A9AO,扩除非汉字区。
*用户自定义区
即GBK区域中的空白区,用户可以自己定义字符。
GB18030是最新的汉字编码字符集国家标准,向下兼容GBK和GB2312标准。GB18030编码是一二四字节变长编码。一字节部分从0x0~0x7F与ASCII编码兼容。二字节部分,首字节从0x81~0xFE,尾字节从0x40~0x7E以及0x80~0xFE,与GBK标准基本兼容。四字节部分,第一字节从0x81~0xFE,第二字节从0x30~0x39,第三和第四字节的范围和前两个字节分别相同。
不一样的中文
中文的问题好像也解决了,且慢,新的问题又来了。
中国的台湾省也在使用中文,但是由于历史的原因,那里没有使用大陆的简体中文,还在使用着繁体的中文,并且他们自己也制定了一套表示繁体中文的字符编码,称为BIG5,不幸的是,虽然他们的也使用两个字节来表示一个汉字,但他们没有象我们兼容ASCII一样兼容大陆的简体中文,他们使用了大致相同的编码范围来表示繁体的汉字。天哪!ISO8859的悲剧又出现在同样使用汉字的中国人身上了,同样的编码在大陆和台湾的编码中实际上表示不同的字符,大陆的玩家在玩台湾的游戏时,经常会遇到乱码的问题,问题根源就在于,大陆的计算机默认字符的编码就是GB2312,当碰到台湾使用BIG5编码的文字时,就会作出错误的转换。
由于历史和文化的原因,日文和韩文中也包含许多的汉字,象汉字一样拥有大量的字符,不幸的是,他们的字符编码也同样与中文编码有着冲突,日文的游戏在大陆上一样也会出现无法理解的乱码。《中文之星》,《南极星》,《四通利方》就是用于在这些编码中进行识别和转换的专用软件。
互联的时代
在二十世纪八十年代后期,互联网出现了,一夜之间,地球村上的人们可以直接访问远在天边的服务器,电子文件在全世界传播,在一切都在数字化的今天,文件中的数字到底代表什么字?这可真是一个问题。
UNICODE
实际上问题的根源在于我们有太多的编码表。
如果整个地球村都使用一张统一的编码表,那么每一个编码就会有一个确定的含义,就不会有乱码的问题出现了。
实际上,在80年代就有了一个称为UNICODE的组织,这个组织制定了一个能够覆盖几乎任何语言的编码表,在Unicode3.0.1中就包含了49194个字符,将来,Unicode中还会增加更多的字符。Unicode的全称是UniversalMultiple- OctetCodedCharacterSet,简称为UCS。
由于要表示的字符如此之多,所以一开始的Unicode1.0编码就使用连续的两个字节也就是一个WORD来表示编码,比如“汉”的UCS编码就是6C49。这样在Unicode的编码中就可以表示256*256=65536种符号了。
直接使用一个WORD相当于两个字节来保存编码可能是最为自然的Unicode编码的方式,这种方式被称为UCS-2,也被称为ISO10646,,在这种编码中,每一个字符使用两个字节来进行表示,例如,“中”使用11598来编码,而大写字母A仍然使用65表示,但它占用了两个字节,高位用0来进行补齐。
由于每个WORD表示一个字符,但是在不同的计算机上,实际上对WORD有两种不同的处理方式,高字节在前,或者低字节在前,为了在UCS-2编码的文档中,能够区分到底是高字节在前,还是低字节在前,使用UCS-2的文档使用了一组不可能在UCS-2种出现的组合来进行区分,通常情况下,低字节在前,高字节在后,通过在文档的开头增加FFFE来进行表示。高字节在前,低字节在后,称为大头在前,即BigEndian,使用FFFE来进行表示。这样,程序可以通过文档的前两个字节,立即判断出该文档是否高字节在前。
Endian这个词出自《格列佛游记》,小人国的内战就源于吃鸡蛋时要先吃大头bigendian还是小头little-endian,并由此发生了内战。
理想与现实
UCS-2虽然理论上可以统一编码,但仍然面临着现实的困难。
首先,UCS-2不能与现有的所有编码兼容,现有的文档和软件必须针对Unicode进行转换才能使用。即使是英文也面临着单字节到双字节的转换问题。
其次,许多国家和地区已经以法律的形式规定了其所使用的编码,更换为一种新的编码不现实。比如在中国大陆,就规定GB2312是大陆软件、硬件编码的基础。
第三,现在还有使用中的大量的软件和硬件是基于单字节的编码实现的,UCS-2的双字节表示的字符不能可靠的在其上工作。
新希望UTF-8
为了尽可能与现有的软件和硬件相适应,美国人又制定了一系列用于传输和保存Unicode的编码标准UTF,这些编码称为UCS传输格式码,也就是将UCS的编码通过一定的转换,来达到使用的目的。常见的有UTF-7,UTF-8,UTF-16等。
其中UTF-8编码得到了广泛的应用,UTF-8的全名是UCSTransformationFormat8,即UCS编码的8位传输格式,就是使用单字节的方式对UCS进行编码,使Unicode编码能够在单字节的设备上正常进行处理。
UTF-8编码是变长的编码,对不同的Unicode可能编成不同的长度
UCS-2UTF-8
0000-007F0-1270xxxxxxx
0080-07FF128-2047110xxxxx10xxxxxx
0800-FFFF2048-655351110xxxx10xxxxxx10xxxxxx
例如1的Unicode编码是3100,在0-127之间,所以转换后即为31,而“中”字的UTF-8Unicode编码为11598,转换成UTF-8则为e4b8ad。
实际上,ASCII字符用UTF-8来表示后,与ASCII是完全一样的,美国人又近水楼台的把自己的问题解决了。但其他的编码就没有这么幸运了。
突破障碍-Unicode与本地编码的转换
UTF-8编码解决了字符的编码问题,又可以在现有的设备上通行,因此,得到了广泛的使用,
XML中的问题
XML的设计目标是实现跨网络,跨国界的信息表示,所以,在XML设计之初,就规定XML文件的默认编码格式就是UTF-8,也就是说,如果没有特殊的说明,XML文件将被视为UTF-8编码。
然而,大部分的中文编辑软件,是根据操作系统来决定编码的方式的,所以,在写字板中直接输入并保存的文件,将被保存为GB2312编码,所以,在读出XML文件内容时,往往就会出现文件错误的提示了。这种情况会出现在文件中有中文出现的时候,如果没有中文,只有英文信息,就不会出现问题。原因很简单,有中文时,因为中文的编码并不是UTF-8编码,所以会造成冲突,没有中文时,英文的编码在GB2312中与ASCII是兼容的,而 ASCII与UTF-8是完全一致的,所以不会出现问题。这种情况也包括UltraEdit软件。
但时,专业的XML编辑软件会自动将内容保存为UTF-8编码,不会有问题。
在通过DOM或XSLT保存XML文件时也有着同样的问题。
默认情况下,XML的处理程序一般会将内容作为UTF-8编码进行处理,所以保存下来的XML文件必须要用可以识别UTF-8的软件来进行查看,如Windows的记事本。
Java的处理
Java的设计目标是一次编写,到处运行,所以在Java的内部对字符的处理采用了UCS来处理,因此Java的字符类型不再是C++中的一个字节,而使用两个字节来保存一个字符。
但是,我们会发现,在Java的文件流中保存为文件后,我们可以直接使用记事本或UltraEdit打开察看。
在这里,Java采用了一个灵巧的默认转换机制,当需要将内容中的字符保存到文件中时,Java会自动的查看一下系统的本地编码,系统的本地编码可以在控制面板中查到,然后,自动将UCS编码的字符转换为本地编码,并进行保存。当需要从系统的文件系统中读入一个文件时,Java通过查看系统的本地编码来决定如何识别文件的内容。这样,Java就可以在内部使用UCS,但用户可以直接使用本地编码的文件了。
Java在相应的方法中,提供了额外的参数,可以让用户自己来指定文件的编码。
.Net的处理
在微软的.Net内部,同样使用UCS编码,但是,在对文件进行处理的时候,与Java有一些区别,.Net不查询系统的本地编码,而是直接使用磨人的UTF-8编码进行文件的处理,所以,你保存的中文内容,在UltraEdit中可能就是乱码,但是,如果你使用记事本打开的话,就不会有问题,因为Windows的记事本可以识别UTF-8的编码。
.Net软件的配置文件使用XML格式,默认的编码一样是UTF-8,所以,必须使用可以识别UTF-8的软件进行处理,如:vs.net,记事本等。
在.Net中,网页默认处理编码就是UTF-8。
Web中的问题
网页的编码问题主要有两点,一是网页是如何编码的,二是如何告诉浏览器如何编码的。
第一个问题,又可以分成静态页面和动态页面两个问题。
对静态页面,网页的编码要看你保存文件时的编码选项,多数的网页编辑软件可以让你选择编码的类型,默认为本地编码,为了使网页减少编码的问题,最好保存为UTF-8编码格式。
对动态页面,如Servlet生成的页面,在HttpServletResponse类中有一个方法setContentType,可以通过参数来指定生成的页面的类型和编码,例如:response.setContentType("text/html;charset=utf-8");来指定生成的页面的编码类型。
对jsp页面可以通过<%@pagecontentType="text/html;charset=gb2312"%>来指定生成的页面的编码及类型。
第二个问题,如何通知浏览器网页的编码类型。
浏览器收到只是一个字节流,它并不知道页面是如何编码的,因此,需要一个机制来告诉浏览器页面的编码类型,标准的机制是使用<metahttp- equiv="Content-Type"content="text/html;charset=utf-8">来指定页面的编码,当浏览器读取页面遇到这样的指示时,将使用这里制定的编码方式重新加载页面。
否则的话,浏览器将会试图猜出页面的编码类型。
Tomcat中的中文问题
在Tomcat中,经常遇到取回客户端提交的信息是乱码的问题。
当提交表单的时候,HTML页面的Form标签会使情况变得更为复杂。浏览器的编码方式取决于当前页面的编码设定,对Form标签也照此处理。这意味着如果 ASCII格式的HTML页面用ISO-8859-1编码,那么用户在此页面中将不能提交中文字符。所以,如果你的页面使用的是utf-8,那么POST的时候,也将使用utf-8。
由于Tomcat是美国人设计的,Tomcat默认使用ISO8859-1编码队客户端返回的内容进行解码,由于编码与内容不一致,就会出现乱码的???出现,根据以上的分析,在服务器端读取客户端回送的内容时,需要首先设定回送内容的编码,然后再进行信息的读取,通过使用HttpServletRequest的方法setCharacterEncoding("utf-8")先行设定信息的编码类型。然后,就可以正确读取内容了。
总结
编码问题是信息处理的基本问题,但是由于历史和政治的问题,事实上存在着大量不统一的编码方式,造成在信息处理过程中的信息丢失,转换错误等问题,UCS为问题的解决提供了一个很好的方向,但是,在现在的软件环境中,还没有达到全面地使用。在实际中工作中应尽量采用统一的编码格式,减少编码问题的发生。
总之:美国佬用7位(127)就解决了自己的问题,欧洲人又扩展了一个1位用8位(256)解决了自己的问题,都在一个字节内。咱中国人的象形字虽不太好处理,但凭国人的智慧用两个字节把自己的问题也解决了。
下面咱就看看在单片机中,一个字符,转变成了什么样的了?
当然编译工作是在计算机上完成的,这个汉字的转化工作也是计算机完成的。程序下载到单片机后,假如我们想要知道,汉字转化后的数据是多少怎么办呢?可惜本人不是计算机开发人员,只能在KEIL环境中用个笨办法来实现。查看转化后的数据。
编写下面的一段程序:
/********************************************
*copyright(c)2014,曲阜XXX
*allrightsreserved.
*name:查看汉字,字母,符号的存储代码
*description:输入一个汉字,字母,符号,通过模拟运行,查看它们被存放的数据,汉字一般两个字节即可,程序中设置了4个字节。
*version:1.1
*author:xiangzhiK
*data:2014-3-11
***************************************************/
#include<STC15F2K.H>//头文件
unsignedcharcma[4]="孔";//定义一个四个元素的数组用来存放字符代码
/*******串行口的初始化函数*****************/
voidUartInit(void)//9600bps@11.0592MHz
{
SCON=0x50;//8位数据,可变波特率
AUXR|=0x40;//定时器1时钟为Fosc,即1T
AUXR&=0xFE;//串口1选择定时器1为波特率发生器
TMOD&=0x0F;//设定定时器1为16位自动重装方式
TL1=0xE0;//设定定时初值
TH1=0xFE;//设定定时初值
ET1=0;//禁止定时器1中断
TR1=1;//启动定时器1
}
/********串行口发送数据函数****************/
voidsenddata(unsignedchardat)
{
SBUF=dat;//数据送入串口寄存器发送
}
/**********主函数**********************/
voidmain()
{
unsignedchari=0;//定义一个局部变量i
UartInit();//调用串口初始化函数
for(i=0;i<4;i++)//循环4次输出4个字节的数组数据
{
senddata(cma[i]);//串行口输出依次输出数组中的数据
}
}
将上面的程序,进行编译,点击debug>start/stopdebugsession打开“运行”界面,
点击peripherals>serial>serial0打开串口0的界面,
点击“watch1”在name下的对话框中双击,然后输入i,以便观察局部变量i的值的变化。
点击“step”或点击F11键,开始单步运行,注意观察“串行口0的界面”中的SBUF0框中的值,你就可以看到如下顺序值:0xBF,0XD7,0X00,0X00.
也就是说“孔”这个汉字,经过处理,变成了一个两字节的数字,0XBF,0XD7.
你可以将unsignedcharcma[4]="孔";中的孔改成其他的文字,比如
unsignedcharcma[4]="度";就会得到0XB6,0XC8;
假如把中文换成字母,结果会是什么呢?
比如unsignedcharcma[4]="a";呵呵,是不是得到了0X61,0X00,0X00,0X00.那么你在对照一下ASCII码表,这个小写的a的十六进制值是不是就是61呢。
说这么多目的是,为了理解程序是如何处理字符的。以便将来自己建立字符库时,明白字符如何在字符库中找到自己对应的库的。
至于如何建立字符库,以及不同的字符找到对应的显示代码的程序编写方法。下一次再作说明。