转载地址: http://tcking.javaeye.com/blog/726643
解析WEB开发编码问题
在进行web开发的时候经常会遇到乱码的问题,乱码一般出现在:
1.写在jsp文件中的中文变成乱码
2.页面的中文都变成乱码
3.后台通过request.getParameter()乱码
计算机只能以字节为单位存储和传输信息,而人需要看的是字符串,字节和字符串之间的对应关系就是字符集,例如字符“中”使用UTF-8字符集映射的字节为:E4 B8 AD 三个字节,同样反过来,这三个字节通过UTF-8字符集映射便能得到“中”这个字符,不同的字符集映射规则不一样,能表示的范围也不一样,例如“中”在GB2312中对应的字节表示为:D6 D0 两个字节,字符和字节之间的转换,描述为编码和解码:
l 字符->字节:编码,例如:“中”的UTF-8编码为 E4 B8 AD
l 字节->字符:解码,例如:字节数组D0 D6根据GB2312解码为字符“中”
还有一类编码称为URI编码和URI解码,不过URI编码和解码不是字符串和字节流之间的转换,而是由一个字符串表示另一个字符串,例如:
l “中”的UTF-8 URI编码为 %E4%B8%AD
l 字符串%E4%B8%AD根据UTF-8进行URI解码为字符“中”
可以看出来,URI编码就是将一个字符串用%+对应字符集的编码组织的字符串来表示的,在java中String类有两个常用的方法进行编码和解码:
l getBytes :例如“中”.getBytes(“字符集”),根据指定的字符集进行编码
l String(bytes[],”字符集”):根据自定的字符集对字节数组进行解码
浏览器和服务器通过网络相连,浏览器请求编码成字节流在网络上传输,应用服务器接收到浏览器发送过来的字节流后通过相应的字符集再解码为字符串,如果浏览器和服务器端使用了不同的字符集或者不兼容的字符集就会导致乱码问题,例如,浏览器将“中”按照UTF-8编码为字节E4 B8 AD,在网络上传输,应用服务器接收到字节后按照GBK进行解码,那么前两个字节首先被解码为“涓”了,这只是个简单的过程说明,实际的过程比这要复杂。
因此要搞清web的编码问题,很有必要先弄清请求的过程和过程中的编码和解码。web是请求响应模式,用户操作浏览器,例如点击一个按钮提交表单,或者点击一个超链接,这时浏览器就会向应用服务器发送请求,servlet容器接受到请求后,根据web.xml的设置调用相应的应用程序,应用程序根据发送的请求进行一定的逻辑处理后返回给浏览器一段html代码,浏览器根据html解析并展现给用户,这就是一次请求应答的过程,下面对各部分作更加详细的介绍:
浏览器向应用服务器发送请求一般通过三种方式:1.提交表单,2.超链接,3.Ajax;
1)表单提交
表单提交又分为post和get两种方法,
采用post方法时浏览器会将表单中的字符串,采用页面的字符集编码为字节流发送到服务器,
采用get方法时,浏览器首先会将表单中的值通过页面的字符集进行URI编码后拼接到action的URL后面发送到应用服务器,例如:
|
在点击提交的时候,URL为 www.google.com.hk/?test=%E4%B8%AD,可以看出“中”字被编码为%E4%B8%AD,因为当前页面的字符集为UTF-8,所以在进行get表单提交时将按照UTF-8字符集进行URI编码。
2)超链接
在超链接中一般会传递参数,有时候也会传递中文,如这段代码:link,浏览器最终发送给服务器的都是asc字符,其中的“中”不属于asc字符集,因此也会对其进行URI编码的,但是不同的浏览器采用的字符集却不一样,如上面的这个超链接片段,在windows7中,不论页面的content="text/html; charset=utf-8"还是content="text/html; charset=GBK"IE8发送的都是www.google.com.hk/?test=%D6%D0,可以看出这是GBK的URI编码,IE8对超链接中的URI编码与页面的编码无关,与系统的默认编码有关;而在windosx xp中,IE8发送的是页面编码采用的字符集进行的URI编码,如果页面编码为GBK,IE6发送的为GBK的页面编码,如果页面编码为UTF-8则只发送UTF-8的URI编码的前两个字节;在其他的浏览器中,如Firefox和chrome,其URI编码采用的是页面的编码。
操作系统 |
浏览器 |
页面编码 |
发送的请求字符串 |
说明 |
windows7 |
IE8中文 |
UTF-8 |
test=%D6%D0 |
windows7中,采用GBK字符集进行URI编码与页面的编码无关 |
windows7 |
IE8中文 |
GBK |
test=%D6%D0 |
|
XP |
IE8中文 |
UTF-8 |
test=%E4%B8%AD |
xp中与采用页面编码的字符集进行URI编码 |
XP |
IE8中文 |
GBK |
test=%D6%D0 |
|
windows2003 |
IE6中文 |
GBK |
test=%D6%D0 |
GBK是正确,UTF-8是不正确 |
windows2003 |
IE6中文 |
UTF-8 |
test=%E4%B8 |
|
-- |
chrome中文,firefox英文 |
UTF-8 |
test=%E4%B8%AD |
采用页面编码的字符集进行URI编码 |
-- |
chrome中文,firefox英文 |
GBK |
test=%D6%D0 |
可以看出,直接在URL中带中文,IE的不同版本在不同的操作系统中进行URI编码的结果可能不一样,chrome和firefox使用编码和表单的get方式的编码一致,因此,直接在链接中写非asc字符是很危险的,因为字符的编码方式和客户端的环境有关。所以为了避免浏览器进行不确定的URI编码,需要在程序中将中文进行URI编码后在放到URL中,JavaScript提供了encodeURI()函数,它提供的是UTF-8的URI编码,也可以通过java.net.URLEncoder.encode(str,"字符集")进行编码
3)ajax
ajax可以指定get方式或者post方式,情况和上面说的类似
在servlet中一般通过request.getParameter()来获得浏览器发送过来的参数,需要注意的是,服务器Servlet的最底层接受到的是InputStream,也就是字节流,request.getParameter()返回的是一个字符串,因此在getParameter()方法的内部存在解码的过程,而解码所采用的字符集根据应用服务器和操作系统的不同有可能不同,ServletRequest接口提供了一个方法:setCharacterEncoding()来设置getParameter解码的字符集,这个方法必需在getParameter之前调用,通过查看tomcate的源代码发现getParameter在第一次调用时会去初始化一个map的对象,map中存储的就是参数名和参数值,这些值就是根据设置的字符集进行解码的,一旦这些对象解码完毕,下次调用就直接从map中取值,而不需要重新去解码了,所以setCharacterEncoding必需在getParameter之前调用才有作用,也有人说这个方法只对post传递参数有效,而对get方法传递的参数无效,对tomcat5确实是这样的,但是对Websphere和apsuic,setCharacterEncoding对post和get同样有效。
应用服务器 |
服务器所在系统默认编码 |
页面编码 |
提交方式 |
URI编码 |
setCharacterEncoding |
getParameter结果 |
备注 |
websphere6.1 |
GBK |
UTF-8 |
POST |
- |
UTF-8 |
正确 |
服务器默认配置 |
POST |
- |
GBK |
错误 |
||||
GET |
UTF-8 |
UTF-8 |
正确 |
||||
超链接 |
GBK |
GBK |
正确 |
||||
tomcat5.5 |
GBK |
UTF-8 |
POST |
|
UTF-8 |
正确 |
未设置URIEncoding和useBodyEncodingForURI |
POST |
|
GBK |
错误 |
||||
GET |
UTF-8 |
UTF-8 |
错误 |
||||
超链接 |
GBK |
GBK |
错误 |
||||
apusic5.1 |
GBK |
UTF-8 |
POST |
|
UTF-8 |
正确 |
服务器默认配置 |
POST |
|
GBK |
错误 |
||||
GET |
UTF-8 |
UTF-8 |
正确 |
||||
超链接 |
GBK |
GBK |
正确 |
从上面的表格可以看出,Websphere6.1,apusic5.1应用服务器的get和post方法其getParameter解码所用的字符集是setCharacterEncoding所指定的字符集,tomcat5的post方法使用的是setCharacterEncoding,但是get方法却不是。在回过头看看这几个试验的过程,浏览器使用post方法时将会采用页面的字符集进行编码成字节流发送到服务器,服务器接收到字节流后根据setCharacterEncoding设定的字符集进行解码,获得字符串,
也就是说,如果使用post方法提交,只要保证“页面的编码的字符集=setCharacterEncoding设置的字符集”那么getParameter获得的值就是正确的,get和超链接的方式类似,
表单使用get提交时,会根据页面的编码进行encodeURI,超链接的方式程序可以根据指定的字符集进行URI编码,
两种方式的共同点是浏览器都会进行URI编码,在Websphere中,get和超链接的方式只要“URI编码的字符集=setCharacterEncoding的字符集”,那么getParameter的结果就是正确的,而使用get提交表单时其“URI编码的字符集=页面编码的字符集”,超链接的URI编码的字符集在上面说过,chrome和Firefox浏览器中URI编码的字符集=页面编码的字符集,但是IE却不是,没有规律。
tomcat5.5中getParameter获取get方法或超链接传过来的参数时默认会用ISO8859-1进行解码,例如浏览器发送UTF-8的编码的请求,tomcat5.5的getParameter使用ISO8859-1解码,这时的结果是错的,如果要获得正确的值,需要在tomcat5.5的getParameter的时候采用UTF-8进行解码,通过设置URIEncoding="UTF-8"或者useBodyEncodingForURI="true",就能让tomcat在的getParameter时采用UTF-8解码(useBodyEncodingForURI="true"表示解码的字符集采用与页面编码相同的字符集),如果不通过配置要并且需要获得正确的值,则需要程序进行转码,因为getParameter是通过ISO8859-1解码的,所有先通过getParameter().getBytes("ISO8859-1")编码成原来的字节数组,然后使用UTF-8字符集解码为字符串:new String(getParameter().getBytes("ISO8859-1"),"UTF-8")
服务器向浏览器发送的也是经过编码成字节流在网络上传输,浏览器接收到字节流之后使用指定的字符集解码成字符串再进行展现,如果这两个环节的字符集不一致也会导致乱码的问题,
例如静态HTML文件或jsp中都是以UTF-8保存的,则需要告诉浏览器用UTF-8来进行解码,
这些操作都相当于在response的头部增加"Content-Type:text/html;charset=UTF-8"信息,
header中的编码信息的优先级要高于html的meta标签,也就是说如果serlvet中设置了setContentType("text/html;charset=UTF-8"),jsp设置了则浏览器会按照UTF-8字符集进行解码,