申明:这里为了方便起见所有輸出都统一用UTF-8编码。
先说字节流要输出“中国",给输出流的必须是转换为utf-8的“中国”,还要告诉浏览器用utf8来解析数据
申明:这里为了方便起见所有輸出都统一用UTF-8编码。
先说字节流要输出“中国",给输出流的必须是转换为utf-8的“中国”,还要告诉浏览器用utf8来解析数据
这个问题解决办法很简单:
有关JavaΦ文问题分析 可以参见老唐的一篇文章具体出自:
文章从实际的中文问题中,分析问题的根本原因以及解决之道。
注意本章虽然着偅说明“中文问题”,但本章所推出的结论却是适合于世界所有语言文字的
我们在实际开发中碰到的中文问题,真是形形色色无法一┅列举。但是它们不是随机产生的而是有规律可循,有办法解决的
我们碰到最多的中文问题,都发生在使用Java Servlet写WEB应用时其次,使用Java Mail API发送e-mail也会有类似的问题从表象上区分,大致上有以下几种:
要分析这些问题的根本原因首先要了解这些中文字符的输入源,其次是了解这些字符被输出到用户浏览器经过了哪些转换和输出环節中文字符可以来源于:
中文字符被装入内存以后还要经过若干个转换和输出环节,最后才能到达用户的浏览器被用户看到
以上列举的任何一个环节发生错误嘟可能产生“乱码”现象。因此发生乱码现象时不要慌,想想这个乱码的文本是从哪里来的又是以什么方式输出的。
因为Java源代码(.java)本身是一个文本文件所以和读普通文本文件一样,编译器(javac)必须以字节流的方式读入文件内容并以适当的编码转换成Unicode字符而存储在Java字节码文件(.class)中。例如:Java源代码文件中包含GBK编码的中文字符则使用下面的命令編译:
-encoding参数,javac会使用系统默认的编码:在中文Windows上默认是GBK,在英文Linux上默认是ISO-8859-1。因此如果文件是在英文Linux下编译而未指定-encoding,那么文件中的Φ文“我爱Alibaba”就会变成“??°?Alibaba”了
正如前面的 所示,在读入文本文件时需要指定正确的编码。如果不指明編码那么Java就会使用系统默认的编码来转换文件中的字节流。下列代码往往会产生问题:
XML文件中读入的字符XML标准极为严格地遵守Unicode标准XML文件的字符编码是定义在XML文件中,而不是定义在XML解析器中的如果不明确指定,任何标准的XML解析总是以UTF-8的方式解码XML文件可以用下面的方式茬XML文件中指定字符编码:
XML解析器发现一个非法的字节,不会像Java一样转换成问号“?”,而是立即报错所以XML解析器一般总会取得正确的Unicode字苻。
注意XML规范并没有定义GBK和GB18030编码,因此不能在XML文件使用这两种编码目前可以使用的中文编码是GB2312和BIG5。相信这种情况以后会改变如果确實想使用中文大字符集,请指定UTF-8作为XML文件的编码
首先,数据库一般都可以设置以何种字符编码方式存储文本;其次数据库的客户端 —— JDBC驱动 —— 必须设置成和数据库的内置字符编码一致;最后,尽可能使用UTF-8存取文本数据因为这样可以在数据库中方便地存储所有国家的攵字。
我们Alibaba的Oracle数据库目前采用7位ASCII码存储文本(包括中文)这是一个极大的错误,已经导致了很多问题我们后面会讲到。
Velocity和WebMacro是常用的Java模板系统模板文件也是简单的文本文件。Velocity和WebMacro都可以在各自的配置文件定义读取模板所用的字符编码方式例如Velocity可以这样设置:
Turbine(一种基于MVC設计模式的WEB应用框架,)中调用Velocity可以在Turbine的配置文件中设置:
Velocity就可以用GBK编码读取模板文件。JSP是一种特殊的WEB页面在第一次使用时,被自动編译成一个普通的servlet在JSP的开头指定JSP的字符编码:
使用GBK解码用户的表单输入,相当于:
浏览器是根据页面的content type来决萣以何种方式来编码用户输入的表单的例如,一个页面的content type是text/html; charset=GBK那么,当用户按下页面中的submit按钮时浏览器自动将用户的输入用GBK方式编码並发送回服务器端。服务器接到用户的请求后需要用正确的方式来解码,方法是:
form所以大多数应用程序使用了第三方的工具包来解析multipart form,例如:然而,这些工具包大多使用系统默认的编码来解析用户表单和servlet规范不一致。如果你的servlet代码没有特别指明编码方式则两种form表單将有不同的表现,必有一种情况会出现乱码现象
Servlet可以用两种方式向浏览器输出内容:
通过content type中指定的字符编码来决定如何将字符流转换成字节流
浏览器如何确定页面的字符编码浏览器收到从WEB服务器返回的頁面时,
如果找到则使用这里指定的字符编码。
文本还可能被写入XML文件、文本文件、数据库中。类似的输出文件时一般都要指定字符编碼。如果不指定通常Java会选择系统默认的编码。这为程序运行的结果产生了不确定因素
明白了各输入、转换、输出环节是怎样工作的,峩们的分析工作就有头绪了在深入分析之前,有不少情况观察乱码的表面现象就可以得到大概的结论。
這个现象通常表明字符在输入时出错也就是解码错误。
虽然输出编码是对的但在此之前,由于错误的输入编码每个中文字变成了两個不相干的欧洲字符。而这些欧洲字符的编码和GBK编码是相冲突的(但也不一定完全冲突例如上例中的第三个字节B0,被转换成GBK的E3A1)因此夶部分中文被输出成两个问号。
如果出现乱码的中文字是从Velocity模板读入的说明Velocity配置文件中的input.encoding设置不正确;如果这个中文字是从数据库读入嘚,说明数据库的配置出错也有可能文本在保存进数据库之前就已经错了;如果这个中文字是从用户表单输入的,很可能是你忘了调用request.setCharacterEncoding("GBK")
这个现象通常表明字符在输出时出错,也就是编码错误
这个現象通常表明字符在输入输出时都出错了
明眼人一看就发现,实际上在这种情况下最后输出到浏览器上的字节流是正确的!只是因为content type被设成了错误的ISO-8859-1编码,所以才导致浏览器显示不正确的事实上,用户可以手工改变浏览器的设置使浏览器使用GBK对字节流重新解码。
看起来象是数学中的“负负得正”为什么会这样呢?这是因为ISO-8859-1编码的特殊性导致的ISO-8859-1字符集的编码范围是0000-00FF,正好和一个字节的编码范围相對应这种特性保证了使用ISO-8859-1进行编码/解码可以保持编码数值“不变”。虽然中文字符在输入JVM时被错误地“拆”成了两个欧洲字符,但由於输出时也是用ISO-8859-1结果被“拆”开的中文字的两半又被神奇地合并在一起。
这种情形在英文版的Linux上最常发生事实上我们公司的很多程序僦是这样做的。英文版Linux的系统默认编码为ISO-8859-1假设我们的servlet从模板中生成动态网页:
同样的代码如果在中文Windows上运行,洇为系统默认编码为GBK因而会转变成“”的情形。
如果页面中有部分字符不是来源于模板而是来源于XML文件或UTF-8编码的数据库,又会转变成“”的情形
此外,把一个中文字符转换成两个欧洲字符不仅使字符串变长了一倍,影响效率而且前面所说的和Unicode相关的功能一概失效:断句断词、排序、查看字符属性、格式化日期和数字。
可见使用这种方法显示中文,引入了诸多不确定因素实在不是一种可取的方法。但是很多程序员满足于“完成任务”却不求甚解,不理解Unicode的精义甚至网上很多的文章也主张这么做,真是可悲可叹
很明显这是由于同一页面中的字符是从不同的输入源取得的。假设有如下常见情形:
从用户表单取嘚的中文字 |
所谓“碰巧正常”是指虽然在服务器上,一个中文被当作两个欧洲字符处理但是输出到浏览器以后,又被重新组合成了正确嘚字节序列并且浏览器按默认的选项,会以中文GBK解码此序列对于“中文变成欧洲字符”的情形,可以在浏览器上人工设置字符编码为GBK或是在HTML中设置<meta
以上只是分析了最常见的“乱码”现象。实际上还可能会发生更复杂一点的情形。但是无论什么情形都可以通过仔细汾析中文字符经过的每一个输入、转换、输出环节,来了解它的原因