深度揭秘乱码问题背后的原因及解决方式

做Web开发的IT人,如果工作中没遇到过几次乱码的问题,估计都不好意思说自己是开发工程师。 :)

而乱码问题也是各种各样,有保存到数据库中是乱码的,有在服务端接收到参数是乱码的,有在后台返回到客户端时候出现乱码的……

这形形色色的乱码问题,如果处理起来不得要领,着实会让开发人员费不少工夫。

本文,将从乱码的产生原因,应用服务器内部对参数的处理各方面详解原理及解决方案。

1

编码

在开发中,只要有IO的地方,都会涉及到编码。例如下面的代码:

System.out.println( "Hello 中国" );

你猜这个会输出什么呢?

这个其实是和你的文件编码有很大关系的,可能会输出

Hello 中国

也有可能会输出成下面这个样子

Hello ???

你可以改动IDE中的文件编码重复试几次。那为什么会产生这个原因呢?

我们来看这行代码执行过程中的调用栈:

我们看到,简单的一句输出,也是有编码的

看看CharsetEncoder,实现类真多啊

再比如我们都无比熟悉的equals方法,先声明常量STR如下:


之后,代码中有如下的逻辑

你认为,这个时候会有输出吗?

答案是看情况

当我把Constant类以UTF-8为编码保存后,把包含if逻辑的代码以GBK保存之后,equals执行时比较的两个参数就变成了下面的样子:


所以这一定是不会为true的。

这也是乱码产生的原因,

 

原因即解码时采用的encoding与编码时用的encoding不一致所造成的。

2

应用服务器内的乱码

Web应用中,乱码的成因和上述分析是一致的

我们向应用服务器发送一个这样的请求:

http://localhost:8080/test/servlet?abc=你好

服务器用request.getParameter("abc")来获取,这个时候有乱码问题吗?

答案依然是It depends.

这次的看情况是要看哪些情况呢?有以下这些。

  • Tomcat的版本
  • 是否单独设置通道的编码
  • 是否有使用统一的编码Filter

Tomcat默认对于不同的通道(Connector),都可以独立设置相关的编码属性,例如默认是下面这样的配置:

<Connector port="8080" protocol="HTTP/1.1"           connectionTimeout="20000"           redirectPort="8443" />

而我们可以增加自定义的编码配置:

<Connector port="8080" protocol="HTTP/1.1"           connectionTimeout="20000"           redirectPort="8443" URIEncoding="UTF-8"/>

红色的URIEncoding即为添加的属性,这个参数是和Tomcat的版本有关系的。

在Tomcat8中,其对应的官方文档是这样说明的:

This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, UTF-8 will be used unless the org.apache.catalina.STRICT_SERVLET_COMPLIANCE system property is set to

true in which case ISO-8859-1 will be used.

也就是不设置

-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=true

那默认的编码会采用UTF-8。

但是在Tomcat的8.0之前版本官方文档里是这样写的说明:

This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.

也就是不特殊指定,默认将使用ISO-8859-1进行编码。

看Connector的构造方法中,也是明确按此进行配置的

public Connector(String protocol) {    setProtocol(protocol);    ...    //注意下面的代码    if (!Globals.STRICT_SERVLET_COMPLIANCE) {        URIEncoding = "UTF-8";        URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH);    }}

这个配置又是如何作用于参数解析的呢?看下面

public void service(org.apache.coyote.Request req,                    org.apache.coyote.Response res)    throws Exception {        // Set query string encoding        req.getParameters().setQueryStringEncoding            (connector.getURIEncoding()); //注意这里,具体去设置的是Parameters类的queryStringEncoding,这个属性会在后面解析URL中包含的参数时用到。}
public void setQueryStringEncoding( String s ) {    queryStringEncoding=s;}

而具体参数处理时,传进去的就是这个queryStringEncoding

processParameters( decodedQuery, queryStringEncoding );
这种情况,在Tomcat8中就不需要再显示的配置URIEncoding了,而之前的版本则需要配置。有上面的代码参照,我们看到,对于URL中传入的参数,除了设置URIEncoding这个配置之外,是没有办法保证的。因为其解析参数时使用的是queryStringEncoding这个参数,因此只有才保证传到Tomcat的参数编码和解码正确了

而如果配置了统一的编码过滤器,则过滤器内设置request的编码一定要在解析参数前,即调用getParameter前设置,否则并不生效。这是因为parameter只会解析一次,之后就放到一个List中直接根据key返回了。

那是不是设置一个统一的编码Filter,一切就万事大吉了呢?

答案还是看情况吧?

恭喜,你会抢答啦!

3

JSP乱码问题

我们都知道在jsp中,可以设置这样一个jsp头

<%@ page contentType="text/html;charset=iso-8859-1" language="java" %>

那这个时候如果你的页面中要输出一些返回的中文数据,这个时候,页面妥妥的出现了乱码。原因自然是iso-859-1不支持中文有关。注意这里charset不写依然是按iso-8859-1为默认值。

这时,你想到了Filter。在Filter中你大胆的设置了

resp.setCharacterEncoding(encoding);

这个时候,页面展示却依然华丽的乱码了。擦,这是啥原因?

那这个时候,在jsp中显示数据的时候,依然还是会出现乱码的,此时注意观察下响应头:


看下面的代码,由于response在输出的时候。会获取设置的encoding
/** * Return the writer associated with this Response. * * @exception IllegalStateException if <code>getOutputStream</code> has *  already been called for this response * @exception IOException if an input/output error occurs */@Overridepublic PrintWriter getWriter()    throws IOException {

    if (usingOutputStream) {        throw new IllegalStateException            (sm.getString("coyoteResponse.getWriter.ise"));    }

    if (ENFORCE_ENCODING_IN_GET_WRITER) {        /*         * If the response‘s character encoding has not been specified as         * described in <code>getCharacterEncoding</code> (i.e., the method         * just returns the default value <code>ISO-8859-1</code>),         * <code>getWriter</code> updates it to <code>ISO-8859-1</code>         * (with the effect that a subsequent call to getContentType() will         * include a charset=ISO-8859-1 component which will also be         * reflected in the Content-Type response header, thereby satisfying         * the Servlet spec requirement that containers must communicate the         * character encoding used for the servlet response‘s writer to the         * client).         */        setCharacterEncoding(getCharacterEncoding());    }

    usingWriter = true;    outputBuffer.checkConverter();    if (writer == null) {        writer = new CoyoteWriter(outputBuffer);    }    return writer;}

而这个encoding是什么设置的呢?
public void setContentType(String type) {
    if (isCommitted()) {        return;    }

    if (SecurityUtil.isPackageProtectionEnabled()){        AccessController.doPrivileged(new SetContentTypePrivilegedAction(type));    } else {        response.setContentType(type); //这里在设置contentType,由于配置中同时包含charset    }}


String[] m = MEDIA_TYPE_CACHE.parse(type);  //这个是在解析contentType参数if (m == null) {    // Invalid - Assume no charset and just pass through whatever    // the user provided.    coyoteResponse.setContentTypeNoCharset(type);    return;}

coyoteResponse.setContentTypeNoCharset(m[0]);

if (m[1] != null) {    // Ignore charset if getWriter() has already been called    if (!usingWriter) {        coyoteResponse.setCharacterEncoding(m[1]);  //这里就用解析出来的参数设置。        isCharacterEncodingSet = true;    }}

而我们一般为了处理这种乱码问题统一写的filter,在请求处理前就先把request和response的encoding都设置好。而这里默认提供的charset为ISO-8859-1,就出了乱码问题了。另外一个在处理JSP时容易出的问题,就是contentType中指定的charset和filter中已经设置的encoding,两者不一致,比如你的jsp中忘记改了,使用的是默认的ISO-8859-1.此时,先通过filter设置的encoding会先于contentType的设置执行,因此,依然会出现乱码问题。

4

总结

在本文中,深入分析了乱码背后产生的原因:编码和解码时采用的encoding不一致。而解决问题的最朴素的道理就是保持多种数据来源编码的一致性,无论是数据库的,文件的,还是输入输出的,都采用一致的编码,可以简少很多问题。另外,许多文件中有一些默认编码,开发中可能不太注意,此处也是容易出现问题的地方。

时间: 2024-10-12 14:10:21

深度揭秘乱码问题背后的原因及解决方式的相关文章

关于jsp乱码问题的产生原因 及 解决方法。

http://blog.csdn.net/caoxiaohong/article/details/1781777 JSP/JDBC MySQL乱码问题JSP的request 默认为ISO8859_1,所以在处理中文的时候,要显示中文的话,必须转成GBK的,如下String str=new String(request.getParameter("name").getBytes("ISO8859-1"),"GBK"); out.println(st

使用pymysql进行定时查询数据不更新的原因及解决方式

用python写了一个小脚本定时查询数据库,输出查询结果并写入文件,发现每次查询的结果都是相同的,但是数据库确实在更新数据. 原因: REPEATABLE READ The default isolation level for InnoDB. It prevents any rows that are queried from being changed by other transactions, thus blocking non-repeatable reads but not phan

AlphaGo深度揭秘

今日,在乌镇围棋峰会人工智能高峰论坛上,AlphaGo之父.DeepMind创始人戴密斯·哈萨比斯(Demis Hassabis)和DeepMind首席科学家大卫·席尔瓦(David Silver)在论坛上透露了关于AlphaGo的重要信息,以及AlphaGo究竟意味着什么?让人们能详细了解到AlphaGo背后的秘密. AlphaGo是什么? AlphaGo 是第一个击败人类职业围棋选手并战胜围棋世界冠军的程序,是围棋史上最具实力的选手之一.2016 年 3 月,在全世界超过一亿观众的关注下,A

深度揭秘:伪基站短信诈骗产业传奇始末!

深度揭秘:伪基站短信诈骗产业传奇始末! 近些年因伪基站短信诈骗的崛起对用户造成了惨痛的经济损失,如今相关部门严打伪基站短信诈骗行业已取得初步成效,但仍有黑产团伙铤而走险继续作案,导致盗刷事件还是不断涌现.乌云君和行业都曾报道过一些伪基站诈骗手段与影响  伪基站 + 钓鱼 = 完美黑产,但还是难以深入背后的产业运作. 在乌云君经过一段时间的调查后,大致摸清了这个行业的详细分工与具体工作形式,今天给大家深度揭秘这个神秘的“伪基站诈骗产业链”是如何运作与分工,他们的目前的状况如何,又是怎样将你的钱洗走

亿级PV超大型网站集群架构图形深度揭秘讲解

猛戳下面地址观看: 亿级PV超大型网站集群架构图形深度揭秘讲解

mysql保存中文乱码的原因和解决办法

当你遇到这个mysql保存中文乱码问题的时候,期待找到mysql保存中文乱码的原因和解决办法这样一篇能解决问题的文章是多么激动人心. 也许30%的程序员会选择自己百度,结果发现网友已经贴了很多类似mysql 中文乱码.php mysql 中文乱码.mysql5.5中文乱码.mysql 乱码.mysql乱码问题.mysql jsp 乱码.mysql jdbc 乱码.mysql 查询乱码.mysql 导入数据乱码等一系列问题,到底哪个是自己要找的能解决自己问题的呀?15%的程序员一看就懵了,剩下15

coreseek常见错误原因及解决方法

coreseek常见错误原因及解决方法 Coreseek 中文全文检索引擎 Coreseek 是一款中文全文检索/搜索软件,以GPLv2许可协议开源发布,基于Sphinx研发并独立发布,专攻中文搜索和信息处理领域,适用于行业/垂直搜索.论坛/站内搜索.数据库搜索.文档/文献检索.信息检索.数据挖掘等应用场景,用户可以免费下载使用 本文为大家整理了coreseek/sphinx中文检索引擎的常见问题和解决方法,感兴趣的同学参考下. Coreseek 是一款中文全文检索/搜索软件,以GPLv2许可协

HttpClient的CircularRedirectException异常原因及解决办法

HttpClient的CircularRedirectException异常原因及解决办法 这两天在使用我自己爬虫抓取网页的时候总是出现 org.apache.http.client.ClientProtocolException at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:909) at org.apache.http.impl.client.AbstractHttpClie

mysql中文乱码解决方式

近期项目使用到mysql.却突然出现了中文乱码问题.尝试了多种方案,最终解决乱码问题,总结一下解决方式,给遇到同样问题的人一点參考. 中文乱码的原因 1.安装mysqlserver的时候编码集设定有问题 2.创建数据库的时候编码集设定有问题 3.创建表的时候编码集设定有问题 4.client的编码集设定有问题 能够通过命令查看编码集: show variables like "%char%"; 经常使用编码集 1.java中的经常使用编码UTF-8;GBK;GB2312;ISO-885