深入浅出URL编码
一、问题:
编码问题是JAVA初学者在web开发过程中经常会遇到问题,网上也有大量相关的文章介绍,但其中很多文章并没有对URL中使用了中文等非ASCII的字
符造成服务器后台程序解析出现乱码的问题作出准确的解释和说明。本文将详细介绍由于在URL中使用了中文等非ASCII的字符造成乱码的问题。
1、在URL中中文字符通常出现在以下两个地方:
(1)、Query String中的参数值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords
=中国
(2)、servlet path,比如:http://search.china.alibaba.com/selloffer/
中国.html
2、出现乱码问题的原因主要是以下几方面:
(1)、浏览器:我们的客户端(浏览器)本身并没有遵循URI编码的规范(http://www.w3.org/International/O-URL-code.html
)。
(2)、Servlet服务器:Servlet服务器的没有正确配置。
(3)、开发人员并不了解Servlet的规范和API的含义。
二、基础知识:
1、一个http请求经过的几个环节:
浏览器(ie firefox)【get/post】------------>Servlet服务器------------------------------->浏览器显示
编码 解码成unicode,然后将显示的内容编码 解码
(1) 浏览器把URL(以及post提交的内容)经过编码后发送给服务器。
(2)
这里的Servlet服务器实际上指的是由Servlet服务器提供的servlet实现ServletRequestWrapper,不同应用服务器的
servlet实现不同,这些servlet的实现把这些内容解码转换为unicode,处理完毕后,然后再把结果(即网页)编码返回给浏览器。
(3) 浏览器按照指定的编码显示该网页。
当对字符串进行编码和解码的时候都涉及到字符集,通常使用的字符集为ISO8859-1、GBK、UTF-8、UNICODE。
2、URL的组成:
域名:端口/contextPath/servletPath/pathInfo?queryString
说明:
1、ContextPath是在Servlet服务器的配置文件中指定的。
对于weblogic:
contextPath是在应用的weblogic.xml中配置。
<context-root>/</context-root>
对于tomcat:
contextPath是在server.xml中配置。
<Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true" crossContext="true"/>
对于jboos:
contextPath是在应用的jboss-web.xml中配置。
<jboss-web>
<context-root>/</context-root>
</jboss-web>
2、ServletPath是在应用的web.xml中配置。
<servlet-mapping>
<servlet-name>Example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
2、Servlet API
我们使用以下servlet API获得URL的值及参数。
request.getParameter("name"); // 获得queryString的参数值(来自于get和post),其值经过Servlet服务器URL Decode过的
request.getPathInfo(); // 注意:pathinfo返回的字符串是经过Servlet服务器URL Decode过的。
requestURI = request.getRequestURI(); // 内容为:contextPath/servletPath/pathinfo 浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。
3、开发人员必须清楚的servlet规范:
(1)
HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request
body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没
有说明这一点。
(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。
(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。
(4) POST提交的数据是作为request body的一部分。
(5) 网页的Http头中ContentType("text/html; charset=GBK")的作用:
(a) 告诉浏览器网页中数据是什么编码;
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。
三、下面我们分别从浏览器和应用服务器来举例说明:
URL:http://localhost:8080/example/
中国?name=中国
汉字 编码 二进制表示
中国 UTF-8 0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]
中国 GBK 0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]
中国 ISO8859-1 0x3f,0x3f[63, 63]信息失去
(一)、浏览器
1、GET方式提交,浏览器会对URL进行URL encode,然后发送给服务器。
(1) 对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。
http://localhost:8080/example/
中国?name=中国
实际上提交是:
GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
(1) 对于中文IE,如果在高级选项中取消总以UTF-8发送,则PathInfo和QueryString是URL encode按照GBK编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
(3) 对于中文firefox,则pathInfo和queryString都是URL encode按照GBK编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。对于中文的IE和FIREFOX都是采用GBK编码QueryString。
小结:解决方案:
1、URL中如果含有中文等非ASCII字符,则浏览器会对它们进行URLEncode。为了避免浏览器采用了我们不希望的编码,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode编码过的字符串%.
比如:
URL:http://localhost:8080/example/
中国?name=中国
建议:
URL:http://localhost:8080/example/Öйú?name=%D6%D0%B9%FA
2、我们建议URL中PathInfo和QueryString采用相同的编码,这样对服务器端处理的时候会更加简单。
2、还有一个问题,我发现很多程序员并不明白URL Encode是需要指定字符集的。不明白的人可以看看这篇文档:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
2、 POST提交
对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType("text/html; charset=GBK")中指定的编码进行对表单中的数据进行编码,然后发给服务器。
在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。
解决方案:
1、从最简单,所需代价最小来看,我们对URL以及网页中的编码使用统一的编码对我们来说是比较合适的。
如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。
(二)、Servlet服务器
Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。下面两个Servlet方法返回的结果都是经过解码的:
request.getParameter("name");
request.getPathInfo();
这里所说的"指定的字符集"是在应用服务器的配置文件中配置。
(1) tomcat服务器
对于tomcat服务器,该文件是server.xml
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" URIEncoding="GBK"/>
URIEncoding告诉服务器servlet解码URL时采用的编码。
<Connector port="8080" ... useBodyEncodingForURI="true" />
useBodyEncodingForURI告诉服务器解码URL时候需要采用request body指定的编码。
(2) weblogic服务器
对于weblogic服务器,该文件是weblogic.xml
<input-charset>
<java-charset-name>GBK</java-charset-name>
</input-charset>
(三)浏览器显示
浏览器根据http头中的ContentType("text/html;
charset=GBK"),指定的字符集来解码服务器发送过来的字节流。我们可以调用
HttpServletResponse.setContentType()设置http头的ContentType。
总结:
1、URL中的PathInfo和QueryString字符串的编码和解码是由浏览器和应用服务器的配置决定的,我们的程序不能设置,不要期望用request.setCharacterEncoding()方法能设置URL中参数值解码时的字符集。
所以我们建议URL中不要使用中文等非ASCII字符,如果含有非ASCII字符的话要使用URLEncode编码一下,比如:
http://localhost:8080/example1/example/
中国
正确的写法:
http://localhost:8080/example1/example/ä¸å½
并且我们建议URL中不要在PathInfo和QueryString同时使用非ASCII字符,比如
http://localhost:8080/example1/example/
中国?name=中国
原因很简单:不同浏览器对URL中PathInfo和QueryString编码时采用的字符集不同,但应用服务器对URL通常会采用相同的字符集来解码。
2、我们建议URL中的URL Encode编码的字符集和网页的contentType的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。
******************************************************************************************************************************************
******************************************************************************************************************************************
jsp,servlet编码问题
关键字: jsp servlet 编码 java
- 本文来自:http://janwer.javaeye.com/blog/150226
- 很不错
- 首先,说说JSP/Servlet中的几个编码的作用
- 在JSP/Servlet中主要有以下几种设置编码的方式:
- 1. pageEncoding ="UTF-8"
- 2. contentType = "text/html;charset=UTF-8"
- 3. request.setCharacterEncoding("UTF-8")
- 4. response.setCharacterEncoding("UTF-8")
- 其中前两个只能用于JSP中,而后两个可以用于JSP和Servlet 中。
- 1、pageEncoding ="UTF-8" 的作用是设置JSP编译成Servlet时使用的编码
- 众所周知,JSP在服务器上是要先被编译成Servlet的。pageEncoding ="UTF-8" 的 作用就是告诉JSP编译器在将 JSP文件编译成Servlet时使用的编码。通常,在JSP内部定义的字符串(直接在JSP中定义,而不是从浏览器提交的数据)出现乱码时,很多都是由 于该参数设置错误引起的。例如,你的JSP文件是以GBK为编码保存的,而在JSP中却指定pageEncoding ="UTF-8" ,就会引起JSP内部定义的字符串为乱码。
- 另外,该参数还有一个功能,就是在JSP中不指定contentType参数,也不使用response.setCharacterEncoding方法时,指定对服务器响应进行重新编码的编码。
- 2、contentType ="text/html;charset=UTF-8" 的作用是指定对服务器响应进行重新编码的编码
- 在不使用response.setCharacterEncoding方法时,用该参数指定对服务器响应进行重新编码的编码。
- 3、request.setCharacterEncoding("UTF-8")的作用是设置对客户端请求进行重新编码的编码。
- 该方法用来指定对浏览器发送来的数据进行重新编码(或者称为解码)时,使用的编码。
- 4、response.setCharacterEncoding("UTF-8")的作用是指定对服务器响应进行重新编码的编码。
- 服务器在将数据发送到浏览器前,对数据进行重新编码时,使用的就是该编码。
- 其次,要说一说浏览器是怎么样对接收和发送的数据进行编码的
- response.setCharacterEncoding("UTF- 8")的作用是指定对服务器响应进行重新编码的编码。同时,浏览器也是根据这个参数来对其接收到的数据进行重新编码(或者称为解码)。所以在无论你在 JSP中设置response.setCharacterEncoding("UTF-8") 或者 response.setCharacterEncoding ("GBK"),浏览器均能正确显示中文(前提是你发送到浏览器的数据编码是正确的,比如正确设置了pageEncoding参数等)。读者可以做个实 验,在JSP中设置 response.setCharacterEncoding("UTF-8"),在IE中显示该页面时,在IE的菜单中选择"查看 (V)"à"编码 (D)"中可以查看到是" Unicode(UTF-8)",而在在JSP中设置 response.setCharacterEncoding("GBK"),在IE中显示该页面时,在IE的菜单中选择"查看(V)"à"编码(D)" 中可以查看到是"简体中文(GB2312)"。
- 浏览器在发送数据时,对URL和参数会进行URL编码,对参 数中的中文,浏览器也是使response.setCharacterEncoding参数来进行 URL编码的。以百度和GOOGLE为例,如果你在百度中搜索"汉字",百度会将其编码为"%BA%BA%D7%D6"。而在GOOGLE中搜索"汉 字",GOOGLE会将其编码为"%E6%B1%89%E5%AD%97",这是因为百度的 response.setCharacterEncoding 参数为GBK,而GOOGLE的的 response.setCharacterEncoding参数为UTF-8。
- 浏览器在接收服务器数据和 发送数据到服务器时所使用的编码是相同的,默认情况下均为JSP页面的 response.setCharacterEncoding参数(或者contentType和pageEncoding参数),我们称其为浏览器编 码。当然,在IE中可以修改浏览器编码(在IE的菜单中选择"查看(V)"à"编码(D)"中修改),但通常情况下,修改该参数会使原本正确的页面中出现 乱码。一个有趣的例子是,在IE中浏览GOOGLE的主页时,将浏览器编码修改为"简体中文(GB2312)",此时,页面上的中文会变成乱码,不理它, 在文本框中输入"汉字",提交,GOOGLE会将其编码为"%BA%BA%D7%D6",可见,浏览器在对中文进行URL编码时,使用的就是浏览器编 码。
- 弄清了浏览器是在接收和发送数据时,是如何对数据进行编码的了,我们再来看看服务器是在接收和发送数据时,是如何对数据进行编码的。
- 对于发送数据,服务器按照response.setCharacterEncoding—> contentType—> pageEncoding的优先顺序,对要发送的数据进行编码。
- 对于接收数据,要分三种情况。一种是浏览器直接用URL提交的数据,另外两种是用表单的GET和POST方式提交的数据。
- 因为各种WEB服务器对这三种方式的处理也不相同,所以我们以Tomcat5.0为例。
- 无论使用那种方式提交,如果参数中包含中文,浏览器都会使用当前浏览器编码对其进行URL编码。
- 对 于表单中POST方式提交的数据,只要在接收数据的JSP中正确request.setCharacterEncoding参数,即将对客户端请求进行重 新编码的编码设置成浏览器编码,就可以保证得到的参数编码正确。有写读者可能会问,那如何得到浏览器编码呢?上面我们提过了,在默认请情况下,浏览器编码 就是你在响应该请求的JSP页面中response.setCharacterEncoding设置的值。所以对于POST表单提交的数据,在获得数据的 JSP页面中request.setCharacterEncoding要和生成提交该表单的JSP页面的 response.setCharacterEncoding设置成相同的值。
- 对于URL提交的数据和表单 中GET方式提交的数据,在接收数据的JSP中设置 request.setCharacterEncoding参数是不行的,因为在Tomcat5.0中,默认情况下使用ISO-8859-1对URL提交 的数据和表单中GET方式提交的数据进行重新编码(解码),而不使用该参数对URL提交的数据和表单中GET方式提交的数据进行重新编码(解码)。要解决 该问题,应该在Tomcat的配置文件的Connector标签中设置 useBodyEncodingForURI或者URIEncoding属性,其中 useBodyEncodingForURI参数表示是否用 request.setCharacterEncoding参数对URL提交的数据和表单中 GET方式提交的数据进行重新编码,在默认情况下,该参数为 false(Tomcat4.0中该参数默认为true);URIEncoding参数指定对所有GET方式请求(包括URL提交的数据和表单中GET方 式提交的数据)进行统一的重新编码(解码)的编码。URIEncoding和 useBodyEncodingForURI区别是,URIEncoding是对所有GET方式的请求的数据进行统一的重新编码(解码),而 useBodyEncodingForURI则是根据响应该请求的页面的request.setCharacterEncoding参数对数据进行的重新 编码(解码),不同的页面可以有不同的重新编码(解码)的编码。所以对于URL提交的数据和表单中GET方式提交的数据,可以修改URIEncoding 参数为浏览器编码或者修改 useBodyEncodingForURI为true,并且在获得数据的JSP页面中 request.setCharacterEncoding参数设置成浏览器编码。
- 下面总结下,以Tomcat5.0为WEB服务器时,如何防止中文乱码
- 1. 对于同一个应用,最好统一编码,推荐为UTF-8,当然GBK也可以。
- 2. 正确设置JSP的pageEncoding参数
- 3. 在所有的JSP/Servlet中设置contentType ="text/html;charset=UTF-8" 或response.setCharacterEncoding("UTF-8"),从而间接实现对浏览器编码的设置。
- 4. 对于请求,可以使用过滤器或者在每个JSP/Servlet中设置request.setCharacterEncoding ("UTF-8")。同时,要修改Tomcat的默认配置,推荐将useBodyEncodingForURI参数设置为true,也可以将 URIEncoding参数设置为 UTF-8(有可能影响其他应用,所以不推荐.)。
****************************************************************************************************************************************
****************************************************************************************************************************************
JSP/SERVLET编码乱码的终极解决方案
经过上面两片文章的介绍,并使用其提出的方案来解决乱码,但如果还是无法解决,就使用下面的必杀招,先看看原理——+++++++++++++++++
JSP/SERVLET中文编码问题的研究总结
JSP/SERVLET中文编码问题的研究总结 收藏
在进行网页编程中我们经常会遇到一些乱码问题,诸 如GET/POST提交的数据在服务器端处理时出现乱码,浏览器显示服务器端响应出现乱码等,ajax交互过程中出现乱码,而更糟糕的是每次遇到问题我们 都要到网上去搜索一堆的解决方案,但是这些方案大都都捉襟见肘,只能解决部分问题。
分析主要原因,是我们对编码基本知识,java语言对编码处理,浏览器/服务器端在请求响应的过程中对编码的处理,URL的编码规则,还有ajax应用编码规则了解不够透彻等导致的。
本文将从以上这几个原因出发,分析乱码出现的原因和解决方案。
一、 编码基础知识
1.1 字符、字符集和编码
字符 :是文字与符号的总称,包括文字、图形符号、数学符号等。
字符集 :就是一组抽象字符的集合。字符集常常和一种具体的语言文字对应起来,该文字中的所有字符或者大部分常用字符就构成了该文字的字符集,比如英文字符集。一组有共同特征的字符也可以组成字符集,比如繁体汉字字符集、日文汉字字符集。字符集的子集也是字符集。
字符和字符集之间的关系可以用下图表示:
1.2 常用字符集
ASCII :
American Standard Code for Information Interchange ,美国信息交换标准码。
目 前计算机中用得最广泛的字符集及其编码,由美国国家标准局(ANSI)制定。它已被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。 ASCII字符集由控制字符和图形字符组成。在计算机的存储单元中,一个ASCII码值占一个字节(8个二进制位),其最高位(b7)用作奇偶校验位。所 谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若 非奇数,则在最高位b7添1。偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。
ISO 8859-1:
ISO 8859 ,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。
ASCII 收 录了空格及94个“可印刷字符”,足以给英语使用。但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的变音字母,故可以使用 ASCII及控制字符以外的区域来储存及表示。除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使 用这个形式来储存及表示。
* ISO 8859-1 (Latin-1) - 西欧语言
* ISO 8859-2 (Latin-2) - 中欧语言
* ISO 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
* ISO 8859-4 (Latin-4) - 北欧语言
* ISO 8859-5 (Cyrillic) - 斯拉夫语言
* ISO 8859-6 (Arabic) - 阿拉伯语
* ISO 8859-7 (Greek) - 希腊语
* ISO 8859-8 (Hebrew) - 希伯来语(视觉顺序)
* ISO 8859-8-I - 希伯来语(逻辑顺序)
* ISO 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
* ISO 8859-10 (Latin-6 或 Nordic) - 北日耳曼语支,用来代替Latin-4。
* ISO 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
* ISO 8859-13 (Latin-7 或 Baltic Rim) - 波罗的语族
* ISO 8859-14 (Latin-8 或 Celtic) - 凯尔特语族
* ISO 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的法语及芬兰语重音字母,以及欧元符号。
* ISO 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。
Unicode:
Unicode( 统 一码、万国码、单一码)是一种在计算机上使用的字符编码。 它是http://www.unicode.org制定的编码机制, 要将全世界常用文字都函括进去。 它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。 1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。 但自从unicode2.0开始,unicode采用了与ISO 10646-1相同的字库和字码,ISO也承诺ISO10646将不会给超出0x10FFFF的UCS-4编码赋值,使得两者保持一致。 Unicode的编码方式与ISO 10646的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的Unicode版本对应于UCS-2,使用16位的编码空间。 也就是每个字符占用2个字节,基本满足各种语言的使用。实际上目前版本的Unicode尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。
UTF:
Unicode 的实现方式不同于编码方式。
一个字符的Unicode编码是确定的,但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。
Unicode 的实现方式称为Unicode转换格式(Unicode Translation Format,简称为 UTF)。
* UTF-8: 8bit 变长编码,对于大多数常用字符集(ASCII中0~127字符)它只使用单字节,而对其它常用字符(特别是朝鲜和汉语会意文字),它使用3字节。
* UTF-16: 16bit 编码,是变长码,大致相当于20位编码,值在0到0x10FFFF之间,基本上就是unicode编码的实现,与CPU字序有关。
汉字编码
:
* GB2312
字集是简体字集,全称为GB2312(80)字集,共包括国标简体汉字6763个。
* BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个。
* GBK字集是简繁字集,包括了GB字集、BIG5字集和一些符号,共包括21003个字符。
* GB18030是国家制定的一个强制性大字集标准,全称为GB18030-2000,它的推出使汉字集有了一个“大一统”的标准。
1.3 为什么会有乱码
在 下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示",例如GB2312编码的"中文"可以用 iso8859-1表示成:"d6 d0 ce c4",utf-8编码的"中文"可以用iso8859-1表示成:" e4 b8 ad e6 96 87"。
Java 中出现乱码主要有两个原因:
l Unicode-->Byte, 如果目标代码集不存在对应的代码,则得到的结果是0x3f。
如:"/u00d6/u00ec/u00e9/u0046/u00bb/u00f9".getBytes("GBK") 的结果是 "?ìéF?ù", Hex 值是3fa8aca8a6463fa8b4.
仔 细看一下上面的结果,你会发现/u00ec被转换为0xa8ac, /u00e9被转换为/xa8a6... 它的实际有效位变长了!这是因为GB2312符号区中的一些符号被映射到一些公共的符号编码,由于这些符号出现在ISO-8859-1或其它一些SBCS 字符集中,故它们在 Unicode中编码比较靠前,有一些其有效位只有8位,和汉字的编码重叠(其实这种映射只是编码的映射,在显示时仔细不是一样的。Unicode 中的符号是单字节宽,汉字中的符号是双字节宽) . 在Unicode/u00a0--/u00ff 之间这样的符号有20个。了解这个特征非常重要!由此就不难理解为什么JAVA编程中,汉字编码的错误结果中常常会出现一些乱码(其实是符号字符), 而不全是'?'字符, 就比如上面的例子。
l Byte-->Unicode, 如果Byte标识的字符在源代码集不存在,则得到的结果是0xfffd.
如:
Byte ba[] = {(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1};
new String(ba,"gb2312");( new String(ba,"gbk"); 输出为" 丂啊 ")
结果是"?啊", hex 值是"/ufffd/u554a". 0x8140 是GBK字符,按GB2312转换表没有对应的值,取/ufffd. (请注意:在显示该uniCode时,因为没有对应的本地字符,所以也适用上一种情况,显示为一个"?"。
二、 Java 语言对编码的处理
在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。
2.1 Byte[] getBytes(String charset)
这 是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按 unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。
2.2 new String(byte[] bytes, String charset)
这 是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上 述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。
三、 JSP/Servlet 汉字编码问题
网上常出现的 JSP/Servlet encoding 问题一般都表现在 browser 或应用程序端,如:
l 浏览器中看到的 Jsp/Servlet 页面中的汉字怎么都成了 ’?’。
l 浏览器中看到的 Servlet 页面中的汉字怎么都成了乱码。
l JAVA 应用程序界面中的汉字怎么都成了方块。
l Jsp/Servlet 页面无法显示 GBK 汉字。
l JSP 页面中内嵌在<%...%>,<%=...%>等Tag包含的 JAVA code 中的中文成了乱码,但页面的其它汉字是对的。
l Jsp/Servlet 不能正确接收 get/post提交的汉字。
l Jsp/Servlet 不能正确接收直接URL中的中文参数
l JSP/Servlet 数据库读写无法获得正确的内容。
隐藏在这些问题后面的是各种错误的字符转换和处理(除第3个外,是因为 Java font 设置错误引起的)。解决类似的字符 encoding 问题,需要了解 Jsp/Servlet 的运行过程,检查可能出现问题的各个点。
(1) jsp 编译成.java
JSP 编 译。Java 应用服务器将根据 JVM 的 file.encoding 值读取 JSP 源文件,编译生成 JAVA 源文件,再根据 file.encoding 值写回文件系统。如果当前系统语言支持 GBK,那么这时候不会出现 encoding 问题。如果是英文的系统,如 LANG 是 en_US 的 Linux, AIX 或 Solaris,则要将 JVM 的 file.encoding 值置成 GBK 。
这里可以设置<%@ page pageEncoding="GBK"%>中的pageEncoding参数来告诉java应用服务器(不同的服务器实现方式可能有所差异)用什么编码来编译JSP文件
(2) .java 编译成.class文件
第 二阶段是由JAVAC的JAVA源码至java byteCode的编译,不论JSP编写时候用的是什么编码方案,经过这个阶段的结果全部是UTF-8的encoding的java源码.JAVAC用 UTF-8的encoding读取java源码,编译成unicode encoding的二进制码(即.class),这是JVM对常数字串在二进制码(java encoding)内表达的规范.
(3) 客户端请求
客 户端请求主要分为get和post方式,通过这两种方式请求的参数中含有汉字,servlet将无法正确解析,这是由于SUN的 J2SDK 中,HttpUtils.parseName 在解析参数时根本没有考虑 browser 的语言设置,而是将得到的值按 byte 方式解析。
这是网上讨论得最多的 encoding 问题。Servlet在默认情况下使用ISO-8859-1对URL提交的数据和表单中提交的数据进行重新编码的,因此通过request.getParameter(“name”)将得到乱码。
对于表单提交方式:
当通过post提交时候,我们可以使用request.setCharacterEncoding(charset)来对请求数据使用charset重新编码。因此可以用request.getParameter(“name”)直接输出。
但当用GET方式提交时则不能用此方法进行请求的重新编码。因此即使在取参数之前调用了request.setCharacterEncoding(charset),也不能直接使用request.getParameter(“name”)输出正确的中文字符。
这 里有一种比较通用的解决方法,即通过new String(request.getParameter(“name”).getBytes(“iso8859-1”),”charset”)根据被提 交请求的初始编码方式(charset)来重新编码输出,而初始编码方式通常指的就是请求表单所在页面的编码。
对于URL的提交方式:
请 求也可以通过url的方式来进行,url中的中文是由操作系统的默认编码方式进行编码的。如果使用的是中文操作系统,那么url中的中文将以GBK编码。 编码后的数据被发送到服务器,tomcat服务器在默认的情况下,把由GBK编码后的url中的中文参数按照iso8859-1重新编码输出。因此直接使 用request.getParameter(“name”)获取参数时产生乱码,这个时候可以使用
new String(request.getParameter(“name”).getBytes(“iso8859-1”),”GBK”) 来对中文参数进行重新编码,从而获得正确结果。
由于不同操作系统的默认编码不同,因此对url的编码也不相同,因此一般情况下url中尽可能不要使用中文,从而使得程序无法正确解析url中的中文,从而产生乱码。
下面以一个例子来讲解从用户请求到最后服务器响应输出的过程中所经历的编码变化。
字符“中文”的gbk编码为d6d0 cec4
User input *(gbk:d6d0 cec4)
browser *(gbk:d6d0 cec4)
web server iso8859-1(00d6 00d 000ce 00c4) class ,
因此需要在class中进行处理:getbytes("iso8859-1")为d6 d0 ce c4,最后再new String("gbk")为d6d0 cec4,
而内存中的unicode编码则为4e2d 6587。
l 用户输入的编码方式和页面指定的编码有关,也和用户的操作系统有关,所以是不确定的,上例以gbk为例。
l 从browser到web server,可以在表单中指定提交内容时使用的字符集,否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数,则其编码往往是操作系统本身的编码,因为这时和页面无关。上述仍旧以gbk编码为例。
l Web server 接收到的是字节流,默认时(getParameter)会以iso8859-1编码处理之,结果是不正确的,所以需要进行处理。但如果预先设置了编码(通过request. setCharacterEncoding ()),则能够直接获取到正确的结果。
l 在页面中指定编码是个好习惯,否则可能失去控制,无法指定正确的编码。
(4) 服务器端响应
Servlet 需 要将 HTML 页面内容转换为 browser 可接受的 encoding 内容发送出去。依赖于各 JAVA App Server 的实现方式,有的将查询 Browser 的 accept-charset 和 accept-language 参数或以其它猜的方式确定 encoding 值,有的则不管。因此采用固定encoding 也许是最好的解决方法。对于中文网页,可在 JSP 或 Servlet 中设置 contentType="text/html; charset=GB2312";如果页面中有GBK字符,则设置为contentType="text/html; charset=GBK",由于IE 和 Netscape对GBK的支持程度不一样,作这种设置时需要测试一下。因为16位 JAVA char在网络传送时高8位会被丢弃,也为了确保Servlet页面中的汉字(包括内嵌的和servlet运行过程中得到的)是期望的内码,可以用 PrintWriter out=res.getWriter() 取代 ServletOutputStream out=res.getOutputStream(). PrinterWriter 将根据contentType中指定的charset作转换 (ContentType需在此之前指定!); 也可以用OutputStreamWriter封装 ServletOutputStream 类并用write(String)输出汉字字符串。对于 JSP,JAVA Application Server 应当能够确保在这个阶段将嵌入的汉字正确传送出去。
对于tomcat服务器,可以通过
response.setHeader() ;
response.setCharacterEncoding() ;
response.setContentType() ;
<%@page contentType="text/html; chareset=gbk"%>
<meta http-equiv="content-type"
content="text/html; charset=gb2312" />
按照从上往下的优先级来对输出进行重新编码,告诉浏览器以什么编码显示。
四、 Ajax 的编码问题
Ajax 分为Get方式和Post方式两种
(1)请求参数编码问题
在使用post方式提交时,ajax默认是以utf-8对被提交参数进行编码的,因此在服务器端只需要直接使用request.getParameter()输出即可。
在使用get方式提交时,ajax是按照页面的编码方式对被提交参数进行编码的,因此在输出时,要按照页面编码进行编码转换,即
new String(request.getParameter(“name”).getBytes(“iso8859-1”),”charset”)
(2)响应参数问题
AJAX 的响应是用UTF-8进行编码的,因此在响应输出时候必须指定为UTF-8编码,否则将出现乱码。
在解决ajax乱码问题时候,推荐前后台都使用utf-8编码,这样就不会有乱码问题了。
----------------------------------------------------------------------------------------------------------------原理结束
下面,将根据上文中讲述的原理,提出在实际运用中如何使出那招“必杀技”——
在request.getParameter("arg")时,如果获取到的是乱码,则直接先将其转换成字节,再编码成UTF-8:
String arg = request.getParameter("arg");
arg = new String(arg.getBytes, "UTF-8");
OK!这样,就直接获取到正确的中文了。至于上文中提出的new String(request.getParameter(“name”).getBytes(“iso8859-1”),”charset”)方法,getBytes()方法中就别带参数“iso8859-1”,带了反而无法得到正确的中文编码。