http://bbs.blueidea.com/thread-2727873-1-1.html
最近被乱码问题烦心,于是决心研究透这个问题,一劳永逸。
遇到个问题。
电脑归根结底之能识别0 1 两个数字,那么我们在浏览器中输入URL必然要经过编码,无论它是什么类型的编码 终归要成为字节流,也就是只有0 1两个数字。我知道浏览器的选项里默认有个 总是以UTF-8对URL进行编码的选项,请问它是什么意思?我看到了一篇文章,我帖在这里。
对于URL中的一些特殊字符,浏览器会自动进行编码。这些字符除了"/?&"等外,还包括unicode字符,比如汉子。这时的编码比较特殊。
IE有一个选项"总是使用UTF-8发送URL",当该选项有效时,IE将会对特殊字符进行UTF-8编码,同时进行URL编码。如果改选项无效,则使用默认编码"GBK",并且不进行URL编码。但是,对于URL后面的参数,则总是不进行编码,相当于UTF-8选项无效。比如"中文.html?a=中文",当UTF-8选项有效时,将发送链接"%e4%b8%ad%e6%96%87.html?a=x4ex2dx65x87";而UTF-8选项无效时,将发送链接"x4ex2dx65x87.html?a=x4ex2dx65x87"。注意后者前面的"中文"两个字只有4个字节,而前者却有18个字节,这主要时URL编码的原因。
当web server(tomcat)接收到该链接时,将会进行URL解码,即去掉"%",同时按照ISO8859-1编码(上面已经描述,可以使用URLEncoding来设置成其它编码)识别。上述例子的结果分别是"ue4ub8uadue6u96u87.html?a=u4eu2du65u87"和"u4eu2du65u87.html?a=u4eu2du65u87",注意前者前面的"中文"两个字恢复成了6个字符。这里用"u",表示是unicode。
如果按他的说法,我请高手来回答:
问题1:不管UTF-8选项是否打开,它只是对URL中的特殊字符进行编码,那么英文26个字母自然是不遵循这个规则,那么请问是怎样编码?
问题2:不管UTF-8选项是否打开,它只是对URL中的特殊字符进行编码,那么附带的参数中的字符 --无论是特殊或是26个英文--又怎样编码的?或分别怎么编码?
目前看的有点成果分享下。
如上,无论URL怎么编码,它都是向下兼容,即无论GB2312 GBK UTF-8在单字节的编码上都兼容ISO-8859-1,因此我们可以不考虑URL参数的编码,如果将编码完成后的二进制流看成一个数组,它都是符合ISO-8859-1标准的,所以我们要考虑的就不需要是传递的参数的编码,而是服务器端的处理。(这里存在一个问题,不过我资料看没看懂)
在jsp中,处理编码自然是request和response两个对象,我们已经知道了request对象传递进来的参数是用ISO-8859-1编码的,那么我们最常用的就是将参数进行转码,即new String(request.getParameter("paramname").getBytes("ISO-8859-1"),"GB2312"),还有个好方法是使用过滤器。推荐这个。
记住你在page中指定的编码与request中的参数编码无关,这个很容易理解,参数是从客户端发送来的,page只是控制服务器端。
举例:
提示:您可以先修改部分代码再运行
说道这里,你也许一位request的编码问题就解决了,其实还早。
你要注意尽管GB2312 GBK UTF-8在单字节编码上兼容ISO-8859-1,但是把经过ISO-8859-1编码的字节还原成为中文就要考虑到底是几个字节为一个中文的问题,因为UTF-8为3个字节表示一个中文,但是GB2312是2个字节,GBK兼容GB2312,所以这里还要考虑URL参数的编码,这也是我跑根纠底的原因,我没有找到资料,但是从上面的例子我们不难猜测出URL后面的参数编码是符合GB2312的,所以我们大胆假设URL参数的编码是与操作系统相关的,(美国的WINDOWS肯定不用GB2312),好了说到这里你可以暂时松口气了,如果你打算开发AJAX,唉 那还要继续专研 。
特例:AJAX。
form有两种常用请求方式,POST GET,对于需要传递过多参数的请求需要用POST,就如同我的这个帖子用GET肯定是不行的,因为它限制URL的长度。如果你采用GET方法来传递少数参数的请求上面我说的request参数编码的方法就足够了,但是如果你采用POST你会发现提交到后台的数据依然是乱码。这是为什么呢?因为我们用AJAX进行POST请求的时候,都要加上这一句:(不然发送不成功)
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
问题就在这里,前面我们说了,对UTF-8选项对URL参数无效,它是用系统默认编码,而上面这句话就是说对URL参数也进行编码(IE指定的类型,默认UTF-8),好了这个就很容易理解了,如果我们按上面的解码方式new String(name.getBytes("ISO-8859-1"),"GB2312") 必然是错误的,因为GB2312占用2个字节表示一个中文,而UTF-8用3个字节表示一个中文,后台得到的自然就是错误,解决办法:
new String(name.getBytes("ISO-8859-1"),"UTF-8") ;好了你会发现后台得到的数据正常拉
。
这里顺带多说几句,AJAX对返回的数据有2种,responseText responseXML,由于AJAX默认是采用UTF-8编码来处理返回的数据,所以如果我们返回的数据不是UTF-8那么就会乱码,解决办法针对responseXML,因为浏览其会将其作为XML文件处理,所以我们只要在服务器端加上
response.setContentType("text/xml");然后在写出out.println("<?xml version=1.0 encoding='GB2312'?>/n");就可以了,这样如果浏览器识别返回文件的类型是XML,它就会采用xml声明中指定的encoding进行编码,乱码自然就没有了,而对于responseText网上有人用VB写了个处理函数,有需求的自己找找看,因为麻烦,所以强烈推荐使用responseXML处理返回类型。
下面我们讨论response的乱码问题。
常规我们解决乱码的方式是在page指令中指定charset=GB2312,事实上这是指定了输出流的编码,即在jsp中在前定page条件下输出 out.println("中文"); 是可以正常得到的,在这种情况下,我们在jsp文件中声明的String变量默认都是GB2312编码,所以输出也不会出错。
但是还要注意一个问题,偶自己实验出来的 。
这个应该是Tomcat的问题。我们都知道jsp文件实际是转化成符合servlet语法的java文件,但是tomcat在转化的时候会检查page指令的charset属性,假如我们没有在page指令中设定charset属性,那么它默认是ISO-8859-1,如果我们使用response.setContentType("charset=GB2312");
语句来替代page指令,那么你会发现打印出的中文依然是乱码。
这个原因是tomcat转换的问题,它如果检查到page指令中指定了编码,就会把jsp文件中的字符全部采用你指定的编码类型进行编码,但是如果你没有在page中指定,那么它不会检查你的response中的设定,而是用默认的ISO编码来对jsp文件中的字符进行编码,那么自然就会出错,你检查tomcat转化的java文件就会发现,2份文件一摸一样,只是String变量一个是正常中文一个乱码。这并不是我们错误指定编码,只是Tomcat在转换时只检查page中的字符编码属性,而不是response中指定的。
如果深入理解jsp的TAG,可以这么考虑,所有在页面中相关的String类型的编码都是由page指定的,response只负责由page指令编码后得到的字符进行再编码然后发送。也就是page负责页面范围的编码,而response只负责应答范围的编码,如果页面范围的编码没有正常解析String,那么即使response是正确的,它发送的也已经是经过错误编码的字符了。
在servlet中不同,servlet中默认的编码是与操作系统相关的,它没有默认值,也就是说我们在java文件中不用考虑字符的编码,因为它是与系统相关的,但是从request参数中接收的参数默认依然是ISO-8859-1。也许你还没想明白:JSP文件也是转换为servlet,为什么servlet中的编码就是与系统相关,而jsp文件中的就是默认ISO-8859-1了呢?其实这是因为jsp的语法规范规定了当jsp文件转化为servlet时对每一个字符都要强制进行编码,如果没有指明,就按ISO-8859-1进行编码;而servlet如果没有指明编码,它就以java规范对字符进行系统默认编码。
参考资料:
http://blog.csdn.net/bohemia/archive/2006/05/20/746500.aspx
http://www.cn-java.com/target/news.php?news_id=3662
原文修改完毕,大功告成,感觉彻底了解了乱码机制,虽然没研究底层源码的实现细节,但是详细以后不太会找不到乱码的原因拉
tomcat有BUG,对INCLUDE的文件解码容易发生错误,但还不是老错。 这就是累死我也找不到原因的原因。