编码与乱码

    web项目肯定会遇见乱码,如何处理乱码是一个非常有意思的事,也是一个非常常见的面试题,网上的答案有很多,不过这些答案有时候灵,有时候不灵...乱码的原因人竟皆知,就是所谓的解析字符集错误,然后累?怎么解决?我新下了个tomcat(官网下的新的哦),仅做几个小测试,觉得浪费时间直接看总结(我也不喜欢看测试- -,其实之前闲着蛋疼出了个视频,都木有人看。。。)

测试一:做一个web项目的乱码

新建一个web项目url,创建一个servlet,并做简单的修改

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String id = request.getParameter("id");
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
        out.println("<HTML>");
        out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
        out.println("  <BODY>");
        out.println(id);
        out.println("  </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();
    }

将项目加入tomcat后启动tomcat...在firefox浏览器中输入:

http://localhost:8080/url/servlet/test?id=你好

 运行结果:

乱码兽出现了(第一次出现乱码还这么开心--!)...
仔细看看url(红框框),发现木有,跟听说中的url不一样...稍微了解一下url会明白,这货少了协议(http://),大概就是所谓的url增强/扩展(浏览器增强),总之浏览器上的url有问题,木有办法,我们需要寻找准确的url

寻找传递的url:firebug,网络,没东西刷新一下就出来了

的确跟传说中的url一样,但后面的菊花究竟是什么意思?在稍微了解一下url明白,url协议仅支持一下字符:数字与字母([0-9a-zA-Z]),简单的特殊字符($-_.+!*'()),有意义的保留字(?&:#等等)详细可以参考RFC 1738 (关键字 alphanumerics)

也就是说后面的菊花是中文转码的结果,想都不用想,肯定是用utf-8转的(万国码嘛),浏览器将你好转换成utf-8字节码传给请求报文,后台将其立即为xx编码的字符后传给响应报文,那么xx是什么呢?

菊花(经验)都能告诉我gbk...默认的编码会有三种,1.utf-8,万国码,真实这个就不会出乱码 2.ios-8859-1,就是单字节码表,或者是assic码表,用这个解析喜欢出奇怪的字符和? 3.gbk(系统编码),我是用的是中文操作系统,用着个解析喜欢出奇怪的中文....这个a怎么看都不想汉字吧

测试二:模拟乱码原因

/**
 * 
 * @author 程猿
 * 模拟产生乱码的原因
 */
public class t1 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String id="你好";
        String a=new String(id.getBytes("utf-8"),"iso-8859-1");
        System.out.println(a);
    }
}

运行结果为:??????
妈蛋,让不让人玩了,我在这整了半小时都没想明白为什么,直到我把ios-8859-1编码表看一遍才明白(你可以用这个图,对比度科里面写的表格编码)...简单的说就是utf-8(包括gbk,或许所有的其他编码)没有ios-8859-1第八位为1的编码,换句话说,加个断点就能看到

可以想象我的控制台将会用utf-8来打印字符,像这种被万国码抛弃的编码,当然就变成?了。。。疑,问号?
关于?(问号)的出现有很多传说,有人说肯定跟iso-8859-1相关,也有人说是跟字符集之间相互转化照成的....想那么多干嘛,试试呗
测试三:如何产生问号 (尽管从测试二我就已经开始认为?就是无法找到字符的意思,但依然要做个测试三,ios与?的传说真的太出名了)

 

/**
 * 
 * @author 程猿
 *
 * 测试不同编码下的转换规则,以下编码简单理解为
 * gb2312 简体中文
 * big5   繁体中文
 * utf-8  万国码
 */
public class t2 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        valueAndEncodeAndTest("龙","big5");
        valueAndEncodeAndTest("龍","big5");
        
        valueAndEncodeAndTest("龙","gb2312");
        valueAndEncodeAndTest("龍","gb2312");
    }
    
    public static void valueAndEncodeAndTest(String key,String encode) throws UnsupportedEncodingException{
        System.out.println(key+"/"+encode+"/"+key.length()+"   "+Arrays.toString(key.getBytes(encode))+":"+new String(key.getBytes(encode),encode));
    }
}

 

运行结果:

龙/big5/1   [63]:?/big5/1   [-64, 115]:龍
龙/gb2312/1   [-63, -6]:龙
龍/gb2312/1   [63]:?

强调一下big5是中文繁体,gb2312是中文简体,utf-8是无敌的万国码,很显然,找不到的编码会返回63,也就是?
简单的说,在字符转换为字节时,如果该字节不存在,会返回63
这样想来如果用单字节的字符集来转换应该永远不会出现?,只是无敌的万国码不理会第八位为1的编码....iso-8859-1的问号率直接对砍(其他编码以1开头远远大于0开头),大概就是这样成就了?与iso的传说吧

还有个小问题,为什么浏览器能显示这些万国码无法解析的字符?浏览器用的是编码是西文

好像找到原因了,只要改下后面就可以了,但是冷静的分析一下"浏览器将你好转换成utf-8字节码传给请求报文,后台将其立即为xx编码的字符后传给响应报文"...我们都是懂socket的男人,传递的都是字节,如果默认都交给res和req的话,拿这些字节是怎么来的怎么回去,对吧。。。所以只要变下页面的编码就没有乱码了...

测试四:修改页面编码

点击查看--字符编码--Unicode,日,乱码解决了....(其实我是在测试浏览器为什么能显示iso后面的编码时发现的,然后才冷静的想象的....),当然了,通过对id的打印或debugger可以看到,在后端时候他的时候依然还是乱码....既然已经知道乱码产生的原因,那只要做如下处理就可以了

/**
 * 修复乱码
 * @author 程猿
 *
 */
public class t4 {

    
    public static void main(String[] args) throws UnsupportedEncodingException {
        String id="你好";
        String a=new String(id.getBytes("utf-8"),"iso-8859-1");
        System.out.println(a);
        System.out.println(new String(a.getBytes("iso-8859-1"),"utf-8"));
    }
}

运行结果肯定是没问题的,那将相应的处理方式放入servlet中,通过debugger或打印可以看到乱码已经消失了,但前台页面却又出现了??,仔细想想原因,应该是将中文"你好"以ios-8859-1编码传递回去(req与res编码总得对应吧),那再做个测试

测试五:查看测试四后的乱码是否可以修复

/**
 * t4解决后依然有乱码的原因
 * @author 程猿
 *
 */
public class t5 {
public static void main(String[] args) throws UnsupportedEncodingException {
    String id="你好";
    System.out.println(Arrays.toString(id.getBytes("iso-8859-1")));
}
}

运行结果可以发现返回的字节全部都是63....63用任何编码解析出来都是?,看来通过修改页面编码的方式已经不能解决任何问题了

 先梳理一下我们已经发现的事情:
1.将字符解析为字节,如果没有发现对应的字节,返回63
2.将字节转换为字符时,如果没有对应字符,会显示?
3.socket与serversocket依靠字节流传递
4.req与res默认以"iso-8859-1"进行字节到字符的编码/转码

我想只要能够修改response组装编码的方式,就能够解决编码问题了,也就是加入

response.setCharacterEncoding("utf-8");

至此乱码已经全部被解决,当然拥有对response的解决方式使我们可以用同样的方式处理request,也就是所谓了(如下)的处理方式

request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");

当然那得是在request将字节将数据转换为字符之前,如果在servlet是设置,恐怕已经太晚了...在request拥有Parameter里面的数据之前,过滤器,很显然有了这个就可以处理乱码问题了,如果这个时候没有解决乱码...记得查看一下你浏览器解码的方式,当然你可以以下标记,显示注明页面中的编码

web中的乱码问题看起来就差不多了,当然既然知道request与response默认是以iso-8859-1进行字符解释的,那通过配置文件进行乱码的修复当然也是可以的,编码有很多有意思的地方,web乱码更甚,毕竟他可能会牵连一些其他边角的细节
乱码原因:编码/解码不统一
编码规则:将字符解析为字节,如果没有发现对应的字节,返回63 字节转换为字符时,如果没有对应字符,会显示?
不可修复:返回字节63将不可修复(只是没找到对应字符显示?可修复)
url增强:在ie浏览器输入框中输入中文参数和firefox中输入中文参数可不一样
默认的非中文路径:你可以尝试如下路径localhost:8080\你好,并查看
操作系统默认字符集的存在(你有办法解决默认下载名为乱码的问题吗?)


比较常见的编码格式,一般乱码是由下面的编码两两对应照成的
utf-8 万国码,可以认为所有字符在里面都有对应的字节,但并非所有字节都能转换为相应的字符
iso-8859-1 常用的默认编码
gbk 中文编码,一般也是中文操作系统的编码,所以修改一下默认字符集的话,会有惊喜哦

改天再改,太碎了

posted on 2014-10-07 23:08  Glimis  阅读(328)  评论(0编辑  收藏  举报