JAVA字符集知识总结.docx_第1页
JAVA字符集知识总结.docx_第2页
JAVA字符集知识总结.docx_第3页
JAVA字符集知识总结.docx_第4页
JAVA字符集知识总结.docx_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

字符集问题总结:声明:以下内容绝大多数是从网上摘抄下来的,根据个人的理解对部分内容进行了更正和添加,谢谢原作者们的无私奉献;一 基础知识篇很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为字节。 再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为计算机。 开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20以下的字节状态称为控制码。 他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的Ascii编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称扩展字符集。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! 等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的全角字符,而原来在127号以下的那些就叫半角字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做 GB2312。GB2312 是对 ASCII 的中文扩展。 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 DBCS(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍: 一个汉字算两个英文字符!一个汉字算两个英文字符 因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案当时的中国人想让电脑显示汉字,就必须装上一个汉字系统,专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么倚天汉字系统才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊! 正在这时,大天使加百列及时出现了一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它Universal Multiple-Octet Coded Character Set,简称 UCS, 俗称 UNICODE。 UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些“半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于半角英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 这时候,从旧社会里(专指做C等面向过程语言,以下问题对于JAVA不存在)走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的一个字符!同时,也都是统一的两个字节,请注意字符和字节两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。 从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。 但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。 如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立那一天吧! UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。受到过网络编程加持的计算机僧侣们都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构,而另一些是采用高位先发送的方式,在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符如果之后的文本是高位在位,那就发送FEFF,反之,则发送FFFE。不信你可以用二进制方式打开一个UTF-X格式的文件,看看开头两个字节是不是这两个字节? (这段可不看)讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入联通两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。 其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。 从网上引来一段从UNICODE到UTF8的转换规则: Unicode 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 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。 而当你新建一个文本文件时,记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,联通的内码是: c1 1100 0001 aa 1010 1010 cd 1100 1101 a8 1010 1000 注意到了吗?第一二个字节、第三四个字节的起始部分的都是110和10,正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了00001 101010,再把各位对齐,补上前导的0,就得到了0000 0000 0110 1010,不好意思,这是UNICODE的006A,也就是小写的字母j,而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有联通两个字的文件没有办法在记事本里正常显示的原因。 而如果你在联通之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。 好了,终于可以回答NICO的问题了,在数据库里,有n前缀的字串类型就是UNICODE类型,这种类型中,固定用两个字节来表示一个字符,无论这个字符是汉字还是英文字母,或是别的什么。 如果你要测试abc汉字这个串的长度,在没有n前缀的数据类型里,这个字串是7个字符的长度,因为一个汉字相当于两个字符。而在有n前缀的数据类型里,同样的测试串长度的函数将会告诉你是5个字符,因为一个汉字就是一个字符。这应该算是windows的智能检测语言的一个小BUG吧;补充篇:Java中的增补字符Java号称对Unicode提供天然的支持,这话在很久很久以前就已经是假的了(不过曾经是真的),实际上,到JDK5.0为止,Java才算刚刚跟上Unicode的脚步,开始提供对增补字符的支持。现在的Unicode码空间为U+0000到U+10FFFF,一共1114112个码位,其中只有1,112,064 个码位是合法的(我来替你做算术,有2048个码位不合法),但并不是说现在的Unicode就有这么多个字符了,实际上其中很多码位还是空闲的,到 Unicode 4.0 规范为止,只有96,382个码位被分配了字符(但无论如何,仍比很多人认为的65536个字符要多得多了)。其中U+0000 到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP)。U+10000及以上的字符称为补充字符。在Java中(Java1.5之后),补充字符使用两个char型变量来表示,这两个 char型变量就组成了所谓的surrogate pair(在底层实际上是使用一个int进行表示的)。第一个char型变量的范围称为“高代理部分”(high-surrogates range,从uD800到uDBFF,共1024个码位), 第二个char型变量的范围称为low-surrogates range(从uDC00到uDFFF,共1024个码位),这样使用surrogate pair可以表示的字符数一共是1024的平方计1048576个,加上BMP的65536个码位,去掉2048个非法的码位,正好是1,112,064 个码位。关于Unicode的码空间实际上有一些稍不小心就会让人犯错的地方。比如我们都知道从U+0000到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP),这个范围内的字符在使用UTF-16编码时,只需要一个char型变量就可以保存。仔细看看这个范围,应该有65536这么大,因 此你会说单字节的UTF-16编码能够表示65536个字符,你也会说Unicode的基本多语言面包含65536个字符,但是再想想刚才说过的 surrogate pair,一个UTF-16表示的增补字符(再一次的,需要两个char型变量才能表示的字符)怎样才能被正确的识别为增补字符,而不是两个普通的字符 呢?答案你也知道,就是通过看它的第一个char是不是在高代理范围内,第二个char是不是在低代理范围内来决定,这也意味着,高代理和低代理所占的共 2048个码位(从0xD800到0xDFFF)是不能分配给其他字符的。但这是对UTF-16这种编码方法而言,而对Unicode这样的字符集呢?在Unicode的编号中,U+D800到U+DFFF是否有字符分配?答案 是也没有!这是典型的字符集为方便编码方法而做的安排(你问他们这么做的目的?当然是希望基本多语言面中的字符和一个char型的UTF-16编码的字符 能够一一对应,少些麻烦,从中我们也能看出UTF-16与Unicode间很深的渊源与结合)。也就是说,无论Unicode还是UTF-16编码后的字 符,在0x0000至0xFFFF这个范围内,只有63488个字符。这就好比最初的CPU被勉强拿来做多媒体应用,用得多了,CPU就不得不修正自己从 硬件上对多媒体应用提供支持了。尽管不情愿,但说到这里总还得扯扯相关的概念:代码点和代码单元。代码点(Code Point)就是指Unicode中为字符分配的编号,一个字符只占一个代码点,例如我们说到字符“汉”,它的代码点是U+6C49。代码单元(Code Unit)则 是针对编码方法而言,它指的是编码方法中对一个字符编码以后所占的最小存储单元。例如UTF-8中,代码单元是一个字节,因为一个字符可以被编码为1 个,2个或者3个4个字节;在UTF-16中,代码单元变成了两个字节(就是一个char),因为一个字符可以被编码为1个或2个char(你找不到比一 个char还小的UTF-16编码的字符,嘿嘿)。说得再罗嗦一点,一个字符,仅仅对应一个代码点,但却可能有多个代码单元(即可能被编码为2个 char)。以上概念绝非学术化的绕口令,这意味着当你想以一种统一的方式指定自己使用什么字符的时候,使用代码点(即你告诉你的程序,你要用Unicode中的第几个字符)总是比使用代码单元更好(因为这样做的话你还得区分情况,有时候提供一个16进制数字,有时候要提供两个)。例如我们有一个增补字符?(哈哈,你看到了三个问号对吧?因为我的系统显示不出这个字符),它在Unicode中的编号是U+2F81A,当在程序中需要使用这个字符的时候,就可以这样来写:Strings=String.valueOf(Character.toChars(0x2F81A);charchars=s.toCharArray();for(charc:chars)System.out.format(%x,(short)c);后面的for循环把这个字符的UTF-16编码打印了出来,结果是d87edc1a注意到了吗?这个字符变成了两个char型变量,其中0xd87e就是高代理部分的值,0xdc1a就是低代理的值。 二 应用篇1. Java代码中的字符编码转换如果你是JVM的设计者,让你来决定JVM中所有字符的表示形式,你会不会允许使用各种编码方式的字符并存?我想你的答案是不会,如果在内存中的Java字符可以以GB2312,UTF-16,BIG5等各种编码形式存在,那么对开发者来说,连进行最基本的字符 串打印、连接等操作都会寸步难行。例如一个GB2312的字符串后面连接一个UTF-8的字符串,那么连接后的最终结果应该是什么编码的呢?你选哪一个都 没有道理。因此牢记下面这句话,这也是Java开发者的共同意志:在Java中,字符只以一种编码形式存在,那就是UTF-16。但“在Java中”到底是指在哪里呢?就是指在JVM中,在内存中,在你的代码里声明的每一个char,String类型的变量中。例如你在程序中这样写charhan=汉;在内存的相应区域,这个字符就表示为0x6C49。可以用下面的代码证明一下:charhan=汉;System.out.format(%x,(short)han);输出是:6c49反过来用UTF-16编码来指定一个字符也可以,像这样:charhan=0x6c49;System.out.println(han);输出是:汉这其实也是说,只要你正确的读入了“汉”这个字,那么它在内存中的表示形式一定是0x6C49,没有任何其他的值能代表这个字(当然,如果你读错了,那结果是什么就不知道了,范伟说:读,读错了呀,那还等于好几亿呢;本山大哥说:好几亿你也没答上,请听下一题)。JVM的这种约定使得一个字符存在的世界分为了两部分:JVM内部和OS的文件系统。在JVM内部,统一使用UTF-16表示,当这个字符被从JVM内部 移到外部(即保存为文件系统中的一个文件的内容时),就进行了编码转换,使用了具体的编码方案(也有一种很特殊的情况,使得在JVM内部也需要转换,不过 这个是后话)。因此可以说,所有的编码转换就只发生在边界的地方,JVM和OS的交界处,也就是你的各种输入输出流(或者Reader,Writer类)起作用的地方。话头扯到这里就必须接着说Java的IO系统。尽管看上去混乱繁杂,但是所有的IO基本上可以分为两大阵营:面向字符的Reader啊Wrtier啊,以及面向字节的输入输出流。下面我来逐一分解,其实一点也不难。面向字符和面向字节中的所谓“面向”什么,是指这些类在处理输入输出的时候,在哪个意义上保持一致。如果面向字节,那么这类工作要保证系统中的文件二进制 内容和读入JVM内部的二进制内容要一致。不能变换任何0和1的顺序。因此这是一种非常“忠实于原著”的做法(偶然间让我想起郭敬明抄袭庄羽的文章,那家 伙,太忠实于原著了,笑)。这种输入输出方式很适合读入视频文件或者音频文件,或者任何不需要做变换的文件内容。而面向字符的IO是指希望系统中的文件的字符和读入内存的“字符”(注意和字节的区别)要一致。例如我们的中文版WindowsXP系统上有一个GBK的 文本文件,其中有一个“汉”字,这个字的GBK编码是0xBABA(而UTF-16编码是0x6C49),当我们使用面向字符的IO把它读入内存并保存在 一个char型变量中时,我希望IO系统不要傻傻的直接把0xBABA放到这个char型变量中,我甚至都不关心这个char型变量具体的二进制内容到底 是多少,我只希望这个字符读进来之后仍然是“汉”这个字。从这个意义上也可以看出,面向字符的IO类,也就是Reader和Writer类,实际上隐式的为我们做了编码转换,在输出时,将内存中的UTF-16编 码字符使用系统默认的编码方式进行了编码,而在输入时,将文件系统中已经编码过的字符使用默认编码方案进行了还原。我两次提到“默认”,是说Reader 和Writer的聪明也仅此而已了,它们只会使用这个默认的编码来做转换,你不能为一个Reader或者Writer指定转换时使用的编码。这也意味着, 如果你使用中文版WindowsXP系统,而上面存放了一个UTF-8编码的文件,当你使用Reader类来读入的时候,它会傻傻的使用GBK来做转换, 转换后的内容当然驴唇不对马嘴!这种笨,有时候其实是一种傻瓜式的功能提供方式,对大多数初级用户(以及不需要跨平台的高级用户)来说反而是件好事。但我们不一样啦,我们都是国家栋梁,肩负着赶英超美的责任,必须师夷长技以治夷,所以我们总还要和GBK编码以外的文件打交道。说了上面这些内容,想必聪明的读者已经看出来,所谓编码转换就是一个字符与字节之间的转换,因此Java的IO系统中能够指定转换编码的地方,也就在字符 与字节转换的地方,那就是(读者:InputSteamReader和OutputStreamWriter!作者:太强了,都会抢答了!)这两个类是字节流和字符流之间的适配器类,因此他们肩负着编码转换的任务简直太自然啦!要注意,实际上也只能在这两类实例化的时候指定编码,是不是很好记呢?下面来写一段小程序,来把“汉”字用我们非常崇拜的UTF-8编码写到文件中!tryPrintWriterout=newPrintWriter(newOutputStreamWriter(newFileOutputStream(c:/utf-8.txt),UTF-8);tryout.write(汉);finallyout.close();catch(IOExceptione)thrownewRuntimeException(e);运行之后到c盘下去找utf-8.txt这个文件,用UltraEdit打开,使用16进制查看,看到了什么?它的值是0xE6B189!(这正是“汉”这个字的UTF-8编码)噢耶!(读者:这,这有什么好高兴的)补充:JAVA中I/0的常用类里:FileInputStreamFileOutputStreamFileReaderFileWriterDataInputStreamDataOutputStream UTF-8 修改版PrintWriterInputStreamReaderOutputStreamWriter只有InputStreamReader和OutputStreamWriter可进行编码转换,其余要么直接读取字节流,要么按照默认的编码转换;2.WEB乱码分析GB2312,GBK与中文网页GBK还是现如今中文Windows操作系统的系统默认编码(这正是几乎所有网页上的,文件里的乱码问题的根源)。我们可以这样来验证,使用以下的Java代码:Stringencoding=System.getProperty(file.encoding);System.out.println(encoding);输出结果为 GBK说到GB2312和GBK就不得不提中文网页的编码。尽管很多新开发的Web系统和新上线的注重国际化的网站都开始使用UTF-8,仍有相当一部分的中文媒体坚持使用GB2312和GBK,例如新浪的页面(加一句,我曾经抓取过整个新浪的网站页面,必须说,新浪网站的编码是我所见过的大型网站中最乱的)。其中有两点很值得注意。页面中meta标签的部分,常常可以见到charset=GB2312,页面中说的GB2312,实际上并不真的是GB2312(惊讶么?)。我们来做个实验,例如找一个GB2312中不存在的汉字“亸”(这个字确实不 在GB2312中,你可以到GB2312的码表中去找,保证找不到),这个字在GBK中。然后你把它放到一个html页面中,试着在浏览器中打开它,然后 选择浏览器的编码为“GB2312”,看到了什么?它完全正常显示!结论不用我说你也明白了,浏览器实际上使用的是GBK来显示。新浪的页面中也有很多这样的例子,到处都写charset=GB2312,却使用了无数个GB2312中并不存在的字符。这种做法对浏览器显示页面并不成 问题,但在需要程序抓取页面并保存的时候带来了麻烦,程序将不能依据页面所“声称”的编码进行读取和保存,而只能尽量猜测正确的编码。 (一般来说,一个编码方案会留取一些空白的编码段用来与特定的编码方案兼容或用做特殊用途,不同的编码方案的空白编码段通常有所不同,因此,可以利用这一点来做一个在大多数情况下都正确的自动编码检测软件)前言 本文章主要讨论了在Java web系统中乱码产生的内在原理, 是认识和解决乱码问题的基础. 如果您对乱码问题还没有一个清晰的概念, 请尝试阅读本文. 另外, 本文也讨论了最近流行的Ajax技术中的乱码问题, 如果您在使用Ajax技术中遇到了乱码, 本文对您也有一定的参考价值。 为什么会出现乱码我们都知道, 在冯诺伊曼(Neumann Jnos)体系的 计算机中, 任何数据都是以二进制的形式存在的。我们在键盘上输入以及我们在屏幕上看到的中文、日文、英文字符,最终都是内存或者硬盘上的二进制数据。那么,这种二进 制数据和屏幕上的中文、日文、英文字符之间相互的关系就需要通过一种映射来表达,我们把这种关系称之为“字符映射表”。 图 1-1 乱码产生的原因 譬如说, 字符集Shift_JIS规定“”这个字保存到存储介质中为“82A4”;而字符集GBK规定存储介质中的“82A4”代表字符“鷛”。 因此,我们在日文系统中把“”这个字符串保存在某个文件中,然后这个文件被带到一个中文系统上,读取这个文件后就产生了乱码,如图1-1。 在计算机的存储介质中,保存的皆为二进制的数据。计算机本身并没有一种方法知道当前的数据是日文的“”还是中文的“鷛”。 任何一段文本(或者字符串)被保存到存储介质中的时候都需要有一个字符映射表与之相对应。我们在处理文本(或者字符串)的时候需要清楚地知道当前文本(或者字符串)的编码方式是什么。 javac encoding Java作为现在最流行的一种开发语言运行在Java虚拟机上。 运行前,需要把Java的源代码编译成byte code,编译后的byte code被Java虚拟机解释执行。 Java虚拟机认为运行在其上的byte code的编码方式是Unicode,而Java源代码以本地的(local)文本文件的形式存在,那么Java编译器(通常是javac.exe)就需要知道当前的Java源文件对应的字符映射表,然后把其中的字符转化为Unicode的字符。 非常幸运地是,一般情况下,Java有一套很好的机制帮助我们完成了后面的种种编码转换工作,而使得编程人员不需要太在意代码的字符集以便把注意力集中在应用程序的逻辑实现上。 假 设我们使用Java编写一个小程序,在控制台打印“”五个字符。(如图2-1) 由于“任何一段文本(或者字符串)被保存到存储介质中的时候都需要有一个字符映射表与之相对应”,所以当我们使用文本编辑器(包括Eclipse等,非 Microsoft Office)把一段文本保存到硬盘上的时候,我们需要指定当前这段文本所对应的编码方式。(一般地,如果不指定字符映射表,文本编辑器会采用系统默认的 字符映射表保存文本。) 也就是说,我们在日文Windows XP下编写的Java源代码会采用MS932这个字符映射表保存到文件中(如图2-1的处)。 图 2-1 编写、编译并运行Java代码因 为Java虚拟机认为运行在其上的byte code的编码方式是Unicode(如图2-1的处),所以Java编译器会把Java源代码编译成Unicode形式的byte code。但是因为计算机本身并没有一种方法知道当前Java源文件的编码方式,所以,如果不指定编码方式的话,默认地,Java编译器会采用系统默认的 字符映射表读取Java源文件。即,如果在Windows XP日文版下编译此Java程序,那么就会使用MS932格式转化其中的字符串,如果在Windows XP中文版下编译此Java程序,那么就会使用MS936格式转化其中的字符串。如果把Windows XP日文版下编写的Java源代码拿到Windows XP中文版下编译自然就会出现错误了。 因此,Java编译器(特指javac.exe)向我们提供了一个-encoding的参数,我们可以使用-encoding告诉Java编译器采用哪种字符映射表来读取Java源代码。 (如 图2-1的处)那么在控制台上可以正确地显示“”又是为什么呢?Java的System.out.println函数,默认地采用当前系统的 默认字符映射表来输出字符串。即,在WindowsXP日文版下会把Unicode的“”按照MS932的格式输出出来;在WindowsXP 中文版下会把Unicode的“”按照MS936的格式输出出来。所以,编译后的Java byte code可以在任何系统上正确地运行。也就是Java所谓的“Write Once, Run Anywhere.” 另外,我们因此也知道,如果使用System.out.println输出的字符串是乱码的话,也并不能说明此字符串是有问题的。 和web相关的编码问题在Java web系统中,我们主要使用HTTP协议在网络上通讯。 我们把浏览器称为HTTP客户端,把web服务器称为HTTP服务端。两者通过请求(request)和响应(response)的方式传递数据。 图3-1 Web中的编码方式设 想在Windows XP日文版上使用IE浏览器提交“”几个字符,然后HTTP服务端再把这几个字符打印在HTTP客户端的屏幕上。(如图3-1) 其中可能在五处发生了字符集的转换,一处是输入的时候,二处是把字符串通过网络提交到服务器的时候,三处是在服务器端处理字符串的时候,四处是把字符串通 过网络返回给客户端的时候,五处是在客户端显示的时候。 因为HTTP协议是一个文本传输协议,所以通过HTTP协议在网络上传输的数据一定是有一个对应的字符集的。一般地,这个字符集是ISO-8859-1。所以在2处和4处“”的编码方式是ISO-8859-1。我们通过实验也可以证明,在1处和5处是HTTP客户端指定的编码方式,在3处是服务器转码后的编码方式。 由于现有的HTTP客户端和服务器端已经帮我们很好的封装了HTTP协议的实现,所以一般我们在做Java Web Programming程序的时候不考虑在网上传递的数据格式。 在Java web系统中指定编码方式在Java web系统中, 我们遇到的最多的项目就是采用JSP和Servlet技术的项目了。那么,在使用JSP和Servlet技术的web系统中,设置字符集的地方可能有五处。 下面我们先来讨论和响应相关的四处。 一、 pageEncoding 我们可以在JSP页面上加入指令(directives) JSP 在运行前会被JSP编译器编译成Servlet,然后服务器加载此Servlet处理客户端请求。 JSP中,指令是传递给JSP编译器的参数,即告诉JSP编译器如何编译JSP。 Page指令中的pageEncoding属性即告诉JSP编译器使用的是哪种字符映射表来读取当前JSP文件的源代码。如果没有指定pageEncoding属性,默认地,JSP编译器采用当前系统的默认字符映射表来读取JSP页面。 图3-1 Page指令的pageEncoding属性譬如,我们经常遇到的在Windows下编写的Java web应用程序发布到Solaris后,JSP不能编译,通常是由于没有指定pageEncoding造成的。二、 ContentType 另外,我们可以在JSP页面上加入含有ContentType的page指令 这个指令的效果等同于 response.setContentType(“text/html; charset=Shift_JIS”);达到的目的有两个。 其一,在响应的HTTP文本中加入Content-type报头(header)。客户端会根据Content-type来读取网络上传输的数据。 其二,通知web容器如何把文本(或者字符串)转化为网络上传输的二进制数据。 需要注意的是,我们使一个字符串在网络上传输和把一个字符串保存到文件中本质上是相同的,我们都需要一个字符映射表来映射字符和byte之间的关系。 图3-2 关于设置ContentType的作用假 设我们需要把“”这个字符串发送给客户端,那么我们可以通过上面两种方式(即page指令和response对象)设置ContentType 为“Shift_JIS”。 设置后,服务器会认为是“”是使用“Shift_JIS”编码的字符串,并且以此变为比特流发送到客户端。 客户端在接收到HTTP响应后并不知道服务器端是“”,它得到的只是一堆比特数据,那么它会根据HTTP响应的报头Content-type中的设置,把这堆比特数据转化为“”。, 一、 Meta Data 最后一个设置字符集的地方就是HTML页面的meta标签。 一般地 这里设置的字符集是告诉浏览器如何显示HTML页面。 图3-3 关于meta data的作用总 结, 对于客户端页面显示乱码,如果服务器端数据正常的话,那么可能是以上四种地方设置有误。如果pageEncoding设置错误,一般表现为JSP页面无法 编译,或者编译后JSP页面中固有的字符串不能正常显示;如果Content Type设置错误,一般表现为JSP页面全部或者大部分为乱码,调整浏览器的显示编码格式后,仍然不能解决;如果meta data设置错误,一般表现为JSP页面全部为乱码,调整浏览器的显示编码格式后,可以解决。 每种变量如果不设置,则采用缺省值。如果不设置pageEncoding,JSP编译器采用当前系统默认的字符映射表来读取JSP文件;如果不设置Content-type,则采用ISO-8859-1来作为Content-Type。 浏览器使用的显示编码格式:优先级从高到低:Meta Data中的charset , ContentType中的charset,pageEncoding中的charset;提交数据的编码方式一般地,客户端提交给服务器的数据有两种形式,GET和POST。 使用GET方式提交数据的时候,HTTP消息中没有报体(Message Body),提交的数据存在于URL中;使用POST提交数据的时候, 提交的数据存在于HTTP消息的报体中。 (另外,最近比较流行使用XMLHtttpRequest对象提交数据,我们将在下一节中讨论。) 无论以哪种方式提交数据,这些数据都要经过编码和URL Encoding两个过程。 图4-1 URL Character Encoding在服务端会做一遍上述编码过程的逆过程,从而得到“”。 那么,问题就是服务端如何知道客户端传递过来的字符串使用什么编码方式? 首先,假设我们在页面表单里输入了“”,那么HTTP客户端(浏览器)会使用什么编码方式对它进行编码?HTTP客户端(浏览器)会使用当前页面的显示字符集对它进行编码。 显示字符集是指显示某个页面的时候所使用的字符集。既不是meta中指定的字符集,也非Content-type中指定的字符集。 但是,浏览器会使用与显示编码格式对应的显示字符集,可直接理解为显示字符集即为显示编码格式;重复一次:浏览器使用的显示编码格式:优先级从高到低:Meta Data中的charset , ContentType中的charset,pageEncoding中的charset;在Microsoft Internet Explorer中,显示字符集可以在下面这里看到。 图4-2 IE的显示字符集在Mozilla Firefox中,显示字符集可以在下面这里看到。图4-3 Firefox的显示字符集客户端会根据当前页面的显示字符集编码当前页面上表单中的数据,并提交到服务端。 或者说,可以通过改变页面的显示字符集来改变提交数据的编码方式。 其次,当数据提交到服务端,服务器端如何知道客户端传递过来的数据是采用什么编码方式?答案是因不同服务器不同而不同。 对 于Tomcat, Tomcat会认为客户端提交的数据全部采用ISO-8859-1的方式编码,所以Tomcat会采用ISO-8859-1的形式解码;而 Weblogic会采用响应客户端页面的编码方式解码。但是无论哪种服务器采用哪种方式,都不能保证是我们想要的! 那么我们如何来指定我们想要的解码方式呢? 在Java web系统中,我们可以采用request.setEncoding()来指定客户端的编码方式。 这行代码即告诉request对象,客户端的编码方式是。由此,我们就可以保证客户端提交的数据和服务器端接收 的数据采用同样的编码方式。 URLDecoder.decode方法原理:URLDecoder.decode只对%后的字符进行转换,对+字符用空格替代,所以对于无%和+的字符,无影响;如果有 ,则有+的会变成空格,有%的会抛异常或转错;注:POST与GET是有区别的:以上讲的有一些问题,对于POST方法提交的参数,使用一个filter来设置request.setCharacterEncoding(encoding如”UTF-8”),可以解决乱码问题,但对于GET方法,该方法仍然不行,原因在于POST方法仅用encoding(如UTF-8等)编码,而GET方法除此外,还对参数进行了URL编码;那么有没有可能也使用一个filter来自动对GET进行特殊解码呢?答案是:1.如果参数是通过HttpServletRequest类对象的getParameter方法得到(即标准的J2EE规范规定的方法),则不可能;原因在于HttpServletRequest接口类只有getParameter方法,而无setParameter方法,因此我们从应用服务器拿到的HttpServletRequest类对象不可能改变Parameter的值(可恶的J2EE规范);(以下属于YY)如果更暴力点,可以看一下具体的应用服务器提供的request对象的类里设置Parameter的方法,强转为该类别再调用该方法设置Parameter的值;如果该类对象的所有改变Parameter的做法都只有包权限甚至private权限,那就更暴力点,使用JAVA的类反射方法,将该对象的访问级别改为public,修改对象值,再将访问级别改回原来值; 当然,以上做法会于具体的应用服务器内部实现藕合,导致无法更换应用服务器,甚至是应用服务器的版本,所以纯属YY;2.如果参数是通过封装过的方法取到的,则可能; 通过在filter中对方法进行判断,如果是GET方法,则取出该request中的所有Parameter参数,将其转为正常值,再填充回封装的对象方法中;关于Ajax系统编码方式的讨论2005年,Ajax作为最热门的名词之一使使用Ajax技术的项目一下多起来。 因为初次使用这种技术,所以其中最大的问题之一就是编码问题。 Ajax 的核心是XMLHttpRequest对象。总体来说, XMLHttpRequest对象是一个简单的对象。 我们可以调用它的send方法向服务器端发送数据,以及可以调用它的responseText和responseXML来接收从服务器端返回的数据。 根据W3C的定义, 我们使用XMLHttpRequest对象的send方法发送的数据总是为Unicode(UTF-8)的编码方式;使用XMLHttpRequest对象的responseText接收的数据根据Content-type中指定的不同而不同。使用XMLHttpRequest对象的responseXML接收的数据必须符合XML规范,且Content-type中的字符集必须和XML的字符集相同。譬如指定返回Shift_JIS编码的XML数据。 response.setContentTy

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论