转载自:http://blog.163.com/aishang_liuxingyu/blog/static/135362120201201232833630/,谢谢!
简单应用系统中的中文编码
也许读者会问,这些跟我的中文问题有什么关系?我从来没有让我的程序使用过UTF-8或者那些古怪的字符串编码。
遗憾的是,每个程序员确实需要关心这些东西。不管你愿意不愿意,一个简单的java应用,已经在你并没有注意地情况下,使用了以上的很多编码。而这些编码过程在程序的处理过程中,就必须要经过很多次的转换,任何一个转换的错误,都可能是中文问题的根源!
一个玩具性质的JSP应用,就已经使用了GB2312,Unicode,UTF-8,还有这些字符串所转换出来的各种形式。一个普通的J2EE应用,涉及的编码更是繁多。
让我们来看一下在一个最简单的JSP页面中,“中文”两个字的各种表现形式。必须要说明一点的是,只有在应用程序完全按照tomcat的推荐方法设置的时候,以下的字符才会是这种形式,任何一个参数的不同,都将造成结果的不同。
3. 字符转换过程
我们已经看到,在一个过于简单的系统中,已经有3种以上的中文编码,繁多的字符串编码模式,组合出各种奇怪的数据。
而所有这些在程序中运转的时候,都会涉及很多转换的过程。
举简单的例子(以下的编码只是举例,并不是说在这个环节不能使用其他的编码):
1. 用户的输入是GB2312,java需要读取成Unicode的字符串
2. 如果在java代码中写了中文,java编译的类文件会用UTF-8来保存
3. 配置文件使用了java的编码模式(\ u4e2d)
4. 数据序列化XML的形式时,会把字符串转换成XML的字符编码(中)
读取的时候当然是相反的过程
Java WEB开发中的中文乱码问题解决
本文对Java JSP/SERVLET开发中的比较常见的中文参数乱码 问题做一个透彻地分析,并试图通过范例帮助大家能轻松理解,同时给出Java WEB中文参数乱码解决的几种方法。
AD:
本文所有范例以UTF-8为例。大家可以根据自己的情况加以替换。
在开始本文之前,假设本文的读者已经熟悉或了解以下技术:
- Java语法
- Java WEB开发的基本概念
- Jsp
- Servlet
- 至少一种支持JSP/SERVLET的Web服务器(包括安装,运行)
浏览器/WEB服务器之间的参数传递原理分析
浏览器/WEB服务器之间的中文参数传递
1,表单(form)中文参数的传递方法。我们使用一个简单的范例说明表单提交时浏览器的对中文参数的处理。
SubmitAsia.html view plaincopy to clipboardprint? <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <form method="get"> <input type="text" name="userName" id="userName"> <input type="submit" value="submit" /> </form> </body> </html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <form method="get"> <input type="text" name="userName" id="userName"> <input type="submit" value="submit" /> </form> </body> </html>
使用任意浏览器打开该文件,在输入框内输入 “你好” 中文2字,然后按submit按钮,我们注意到浏览器的地址栏:
file:///C:/SubmitAsia.html?userName=%E4%BD%A0%E5%A5%BD
刚才输入“你好”二字,被转换为 %E4%BD%A0%E5%A5%BD 后被发往服务器。
这个 %E4%BD%A0%E5%A5%BD 是什么呢?
我们先使用一个Java程序来测试一下。如下:
EnDecoderUtil.java view plaincopy to clipboardprint? import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; public class EnDecoderUtil { public static void main(String []args) { try { String str = URLEncoder.encode("你好", "UTF-8"); System.out.println(str); str = URLDecoder.decode(str, "UTF-8"); System.out.println(str); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; public class EnDecoderUtil { public static void main(String []args) { try { String str = URLEncoder.encode("你好", "UTF-8"); System.out.println(str); str = URLDecoder.decode(str, "UTF-8"); System.out.println(str); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }
编译执行:
c:\>javac EnDecoderUtil.java
c:\>java EnDecoderUtil
%E4%BD%A0%E5%A5%BD
你好
我们发现,浏览器发送给服务器的“你好”中文参数跟使用java.net.URLEncoder.encode()方法编码后的值完全一样。
原来,浏览器在向服务器传递参数时,对于非数字,非英文的字符(比如中日韩文)时,会先将其加以变换(编码),再发送给服务器,服务器接收到这种格式的字符时,会将其反向编码,还原成原来的字符。
浏览器/Java WEB服务器之间的中文参数传递 过程模拟
为了帮助大家能更好地理解,我们使用下面的例子,该例通过联结的形式向Google服务器发送一个查询命令参数。
比如,我们通过Google查询“你好啊”,通过以下2种方法向Google服务器发送参数:
SubmitAsia2Google.html view plaincopy to clipboardprint? <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> 方法1:<a href="http://www.google.com/search?q=你好啊">你好啊</a><br> 方法2:<a href="http://www.google.com/search?q=%E4%BD%A0%E5%A5%BD%E5%95%8A">你好啊</a> </body> </html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> 方法1:<a href="http://www.google.com/search?q=你好啊">你好啊</a><br> 方法2:<a href="http://www.google.com/search?q=%E4%BD%A0%E5%A5%BD%E5%95%8A">你好啊</a> </body> </html>
使用任意浏览器打开该文件。
方法1:你好啊
方法2:你好啊
使用方法1时,Google的查询页面通常会显示乱码,方法2时显示完全正常。
通过这个例子,我们知道,为了让服务器能够正常接收中文参数,对HTML页面的中文参数一定要经过编码处理。
表单里的中文字符在提交时,浏览器已经替我们做了编码处理,但联结(<a href.../>)里的中文需要我们自己处理。
JSP页面联结的中文参数编码方法
JSP页面里的联结很多情况下是动态生成的,比如根据数据库里的数据的不同动态生成包含中文关键字的联结等等。
方法1:JSP里直接使用java.net.URLEncoder.encode()。例:<a href="some.jsp?key=<%=java.net.URLEncoder.encode("可能包含中文的参数","UTF-8")%>">联结</a>
方法2:Javabean使用java.net.URLEncoder.encode()
在Javabean里使用java.net.URLEncoder.encode()处理之后,JSP里加以引用。
view plaincopy to clipboardprint? <jsp:useBean id="someBean" class="Beans.SomeBean" scope="request" /> ... <% String chars = myBean.getSomeProp(); out.println("<a href=\"some.jsp?key=" + chars + ">联结</a>"); %> ... <jsp:useBean id="someBean" class="Beans.SomeBean" scope="request" /> ... <% String chars = myBean.getSomeProp(); out.println("<a href=\"some.jsp?key=" + chars + ">联结</a>"); %> ...
方法3:使用自定义标签。
在自定义标签里使用java.net.URLEncoder.encode()方法处理。
关于自定义标签的具体方法,这里不做介绍。
JSP与SERVLET的连动
JSP经过上面的处理之后,最后输出的HTML页面联结已经可以正常向服务器传递中文参数了。
下面我们阐述一下Servlet里怎么接收/解析中文参数。
对于<a href="/someServlet?key=%E4%BD%A0%E5%A5%BD">你好</a>之类的联结,我们可以用下面的servlet来解析传递过来的中文参数。
GetAsiaCharServlet.java view plaincopy to clipboardprint? import java.io.IOException; import java.net.URLEncoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class GetAsiaCharServlet extends HttpServlet { @Override //redir?key=xxxx protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String key = req.getParameter("key"); key = <SPAN style="COLOR: #ff0000">new String(key.getBytes("ISO-8859-1", "utf-8"))</SPAN>; System.out.println(keyword); //... //重定向处理 //res.sendRedirect("http://www.google.com/search?q="+URLEncoder.encode(key, "utf-8")); } } import java.io.IOException; import java.net.URLEncoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class GetAsiaCharServlet extends HttpServlet { @Override //redir?key=xxxx protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String key = req.getParameter("key"); key = new String(key.getBytes("ISO-8859-1", "utf-8")); System.out.println(keyword); //... //重定向处理 //res.sendRedirect("http://www.google.com/search?q="+URLEncoder.encode(key, "utf-8")); } }
我们注意到使用req.getParameter("key")得到参数后,还使用了new String(key.getBytes("ISO-8859-1", "utf-8"))把ISO-8859-1字符集形式转换成UTF-8形式。
为什么呢?因为iso-8859-1是Java中网络传输使用的标准字符集,req.getParameter("key")得到的还是ISO-8859-1字符集,所以要转换一下才不会是乱码。
最后,顺便提一下,采用servlet重定向时,也需要对包含中文文字的参数做特殊处理。
例如,SERVLET从HTML页面的联结接受参数,然后重新定向到Google搜索。则可以在上面的GetAsiaCharServlet里加上如下处理:res.sendRedirect("http://www.google.com/search?q="+URLEncoder.encode(key, "utf-8"));
也就是说,需要把参数取出来,转换,再重新使用URLEncoder.encode编码,这样就不会出现乱码现象。
关于 Java Web技术开发中中文乱码问题的深入探讨
摘要:在利用Servalete/Jsp 技术开发 Web应用程序的时候,不可避免的会遇到中文乱码问题。如:页面编码不一致;表单get提交时产生乱码问题;表单使用post方式时产生的乱码问题等。本文首先详细分析这些中文乱码问题产生的原因,并以图例清晰具体的展现出来,最后在此基础上提出详细合理的解决方案。
关键字:Java Web技术开发 中文乱码 字符集
1. 引言
为了能在计算机中表示不同语言中字符,每个国家(或区域)都规定了计算机信息交换用的字符编码集,如美国的ASCII,中国GB2312和GBK,日本的JIS等。
Java语言内部用Unicode来表示字符,Unicode被称为统一的字符编码标准集,它为几乎每种语言中的字符设定了唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。这也使得Java具有良好的可移植性,符合其国际化的思想。
然而在实际应用中,由于应用程序的运行环境不同,和各个本地字符集的补充、完善,以及系统或应用程序实现的不规范,转码时出现的中文乱码问题时时困扰着程序员和用户。
2. 中文乱码产生的原因
Java中文问题一直困扰着很多初学者,如果了解了Java系统的中文问题原理,我们就可以对中文问题能够采取根本的解决之道。
最古老的解决方案是使用String的字节码转换,这种方案问题是不方便,我们需要破坏对象封装性,进行字节码转换。
还有一种方式是对J2EE容器进行编码设置,如果J2EE应用系统脱离该容器,则会发生乱码,而且指定容器配置不符合J2EE应用和容器分离的原则。
在Java内部运算中,涉及到的所有字符串都会被转化为UTF-8编码来进行运算。那么,在被Java转化之前,字符串是什么样的字符集? Java总是根据操作系统的默认编码字符集来决定字符串的初始编码,而且Java系统的输入和输出的都是采取操作系统的默认编码。
因此,如果能统一Java系统的输入、输出和操作系统3者的编码字符集合,将能够使Java系统正确处理和显示汉字。这是处理Java系统汉字的一个原则, 但是在实际项目中,能够正确抓住和控制住Java系统的输入和输出部分是比较难的。J2EE中,由于涉及到外部浏览器和数据库等,所以中文问题乱码显得非常突出。
J2EE应用程序是运行在J2EE容器中。在这个系统中,输入途径有很多种:一种是通过页面表单打包成请求 (request)发往服务器的;第二种是通过数据库读入;还有第3种输入比较复杂,JSP在第一次运行时总是被编译成Servlet,JSP中常常包含 中文字符,那么编译使用javac时,Java将根据默认的操作系统编码作为初始编码。除非特别指定,如在Jbuilder/eclipse中可以指定默 认的字符集。
输出途径也有几种:第一种是JSP页面的输出。由于JSP页面已经被编译成Servlet,那么在输出时,也将根据操作系统的默认编码来选择输出编码,除非指定输出编码方式;还有输出途径是数据库,将字符串输出到数据库。
由此看来,一个J2EE系统的输入输出是非常复杂,而且是动态变化的,而Java是跨平台运行的,在实际编译和运行中,都可能涉及到不同的操作系统,如果任由Java自由根据操作系统来决定输入输出的编码字符集,这将不可控制地出现乱码。
正是由于Java的跨平台特性,使得字符集问题必须由具体系统来统一解决,所以在一个Java应用系统中,解决中文乱码的根本办法是明确指定整个应用系统统一字符集。
指定统一字符集时,到底是指定ISO8859_1 、GBK还是UTF-8呢?
(1)如统一指定为ISO8859_1,因为目前大多数软件都是西方人编制的,他们默认的字符集就是ISO8859_1,包括操作系统Linux和数据库MySQL等。这样,如果指定Jive统一编码为ISO8859_1,那么就有下面3个环节必须把握:
A 开发和编译代码时指定字符集为ISO8859_1。
B 运行操作系统的默认编码必须是ISO8859_1,如Linux。
C 在JSP头部声明:。
(2)如果统一指定为GBK中文字符集,上述3个环节同样需要做到,不同的是只能运行在默认编码为GBK的操作系统,如中文Windows。
统一编码为ISO8859_1和GBK虽然带来编制代码的方便,但是各自只能在相应的操作系统上运行。但是也破坏了Java跨平台运行的优越性,只在一定范围内行得通。例如,为了使得GBK编码在linux上运行,设置Linux编码为GBK。
那么有没有一种除了应用系统以外不需要进行任何附加设置的中文编码根本解决方案呢?
将Java/J2EE系统的统一编码定义为UTF-8。UTF-8编码是一种兼容所有语言的编码方式,惟一比较麻烦的就是要找到应用系统的所有出入口,然后使用UTF-8去“结扎”它。 一个J2EE应用系统需要做下列几步工作:
A 开发和编译代码时指定字符集为UTF-8。JBuilder和Eclipse都可以在项目属性中设置。
B 使用过滤器,如果所有请求都经过一个Servlet控制分配器,那么使用Servlet的filter执行语句,将所有来自浏览器的请求(request)转换为UTF-8,因为浏览器发过来的请求包根据浏览器所在的操作系统编码,可能是各种形式编码。关键一句:
request.setCharacterEncoding("UTF-8")。
网上有此filter的源码,Jdon框架源码中com.jdon.util.SetCharacterEncodingFilter ,需要配置web.xml 激活该Filter。
C 在JSP头部声明:。
D 在Jsp的html代码中,声明UTF-8:
E 设定数据库连接方式是UTF-8。例如连接MYSQL时配置URL如下:
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
一般数据库都可以通过管理设置设定UTF-8
F 其他和外界交互时能够设定编码时就设定UTF-8,例如读取文件,操作XML等。
在Web应用中,通常都包括了浏览器、Web服务器、Web应用程序和数据库等部分。若不指定编码格式,浏览器会根据本地系统默认的字符集(如:GB2312)提交数据,而Web容器默认采用的是ISO-8859-1的编码方式解析Post数据,另外JDBC驱动程序多数也采用ISO-8859-1的编码方式,因此,在Web应用程序运行过程中,输入的中文字符往往需要在不同的字符集之间来回转换,这就导致了中文乱码问题的频繁出现。
图1描述了在Web应用的请求相应过程中,发生的字符编码转换过程,其中浏览器是IE6.0,Web容器是tomcat5.5
因此,对于中文问题的处理,应该在图1的①、②、④的位置明确指定使用同一的支持中文的字符编码集。
3. 中文乱码问题的解决方案
通过对中文乱码产生原因的分析,结合容易产生该问题的具体场景,提出以下的解决方案。
3.1 最基本的乱码问题。
这个乱码问题是最简单的乱码问题。一般新人会出现。就是页面编码不一致导致的乱码。
①<%@ page language="java" pageEncoding="UTF-8"%>
②<%@ page contentType="text/html;charset=iso8859-1"%>
<html>
<head>
<title>中文问题</title>
③<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
</head>
<body>
我是个好人
</body>
</html>
三个地方的编码。
第一个地方的编码格式为jsp文件的存储格式。Ecljpse等编辑jsp的环境会根据这个编码格式保存并编译jsp文件,编辑时要将编辑jsp的环境设置成这样的编码格式载入。
第二处编码为解码格式,即浏览器按照什么格式显示网页。缺省是使用iso8859-1的编码格式。因为iso8859-1不含中文字符,这样如有中文肯定出现乱码。也就是要换成GB2312或UTF-8。此句必须,若不设置有时浏览器不知道用什么编码方式显示网页。当此句设置的和第一句相同时,可以省略第一句。
第三处编码为html控制浏览器的解码方式。如果前面的两处都无误的话,这个编码格式没有关系。有的网页出现乱码,就是因为浏览器不能确定使用哪种编码格式。因为页面有时候会嵌入页面,导致浏览器混淆了编码格式。出现了乱码。
3.2表单get提交方式的乱码处理方式。
如果使用get方式提交中文,接受参数的页面也会出现乱码,这个乱码的原因也是tomcat的内部编码格式iso8859-1导致。Tomcat会以get的缺省编码方式iso8859-1对汉字进行编码,编码后追加到url,导致接受页面得到的参数为乱码。
解决办法:
A, 使用上例中的第一种方式,对接受到的字符进行解码,再转码。
B, Get走的是url提交,而在进入url之前已经进行了iso8859-1的编码处理。要想影响这个编码则需要在server.xml的Connector节点增加useBodyEncodingForURI="true"
属性配置,即可控制tomcat对get方式的汉字编码方式,上面这个属性控制get提交也是用request.setCharacterEncoding ("UTF-8")所设置的编码格式进行编码。所以自动编码为utf-8,接受页面正常接受就可以了。但我认为真正的编码过程是,tomcat又要根据
<Connector port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" useBodyEncodingForURI="true"disableUploadTimeout="true" URIEncoding=”UTF-8”/>
里面所设置的URIEncoding=”UTF-8”再进行一次编码,但是由于已经编码为utf-8,再编码也不会有变化了。如果是从url获取编码,接受页面则是根据URIEncoding=”UTF-8”来进行解码的。
3.3表单使用Post方式提交后接收到的乱码问题
这个问题也是一个常见的问题。这个乱码也是tomcat的内部编码格式iso8859-1在捣乱,也就是说post提交时,如果没有设置提交的编码格式,则 会以iso8859-1方式进行提交,接受的jsp却以utf-8的方式接受。导致乱码。既然这样的原因,下面有几种解决方式,并比较。
A, 接受参数时进行编码转换
String str = new String(request.getParameter("something").getBytes("ISO-8859-1"),"utf-8") ; 这样的话,每一个参数都必须这样进行转码。很麻烦。但确实可以拿到汉字。
B, 接受此参数的页面,执行请求的编码代码, request.setCharacterEncoding("UTF-8"),把提交内容的字符集设为UTF-8。这样的话,直接使用
String str = request.getParameter("something");即可得到汉字参数。但每页都需要执行这句话。这个方法也就对post提交的有效 果,对于get提交和上传文件时的enctype="multipart/form-data"是无效的。稍后下面单独对这个两个的乱码情况再进行说明。
C, 为了避免每页都要写request.setCharacterEncoding("UTF-8"),建议使用过滤器对所有jsp进行编码处理。如:
在web.xml里面加
<filter>
<filter-name>char</filter-name>
<filter-class>com.bbs.common.CharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>char</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
CharacterFilter类
package com.bbs.common;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class CharacterFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("filter....");
request.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
3.4数据库中的中文
对于大多数数据库的JDBC驱动程序,在Java程序和数据库之间传递数据都是以ISO-8859-1为默认编码格式,所以,在程序中向数据库存储包含中文的数据时,驱动程序受限把程序内部Unicode编码格式的数据转换为ISO-8859-1编码,然后传递到数据库中;在数据库保持数据时,默认即以ISO-8859-1保持,这就是为什么常常在数据库中读取中文数据时,读到的是乱码。
要解决上述问题 只需要将数据库默认的编码格式改为GBK或GB2312即可。
4.总结
其实Servate/JSP的中文encoding并没有想象的那么复杂,虽然问题产生的原因多种多样,然而只要我们理解了字符编码的转换过程,仔细分析乱码产生的原因,找到问题的关键,就能对症下药,解决问题。不过,随着中文字符集的变化,不仅仅是Java编程,中文信息处理中的问题还是会存在一段时间的。