编码解码乱码介绍以及乱码的解决方法

原文地址1(编码解码原理,乱码原因和解决方法,这一篇就够了):https://www.cnblogs.com/Alandre/p/3627623.html
原文地址2(乱码的解决方法):http://bbs.itheima.com/forum.php?mod=viewthread&tid=390747&page=1

编码,就是以特定的编码规则将字符编码成字节。

解码,是将字节以特定的编码规则转换为字符。

乱码:字符以某种特定的编码方式转换成字节,字节又以另一种编码方式转换成字符,因为在这个过程中编码和解码所用的编码方式不同,导致乱码。所以乱码经常发生在网络I/O过程中,客户端和服务器的编码方式不同导致乱码。

一次HTTP请求的编码示例:
编码解码乱码介绍以及乱码的解决方法_第1张图片
解决方法,只要将客户端发送请求和服务器接收请求的编码方式统一,服务器返回响应和客户端接收响应的编码方式统一,就能解决乱码。

乱码过程如图(对应上图中客户端到服务器或者服务器到客户端的乱码):
编码解码乱码介绍以及乱码的解决方法_第2张图片
当使用地址栏提交查询参数时,如果不编码,非英文字符会按照操作系统的字符集进行编码提交到服务器

关于response和request的中文乱码问题

一、首先,我们得弄清楚response和request乱码具体是指什么?

response乱码是指:服务器向浏览器发送的响应数据中存在中文,浏览器显示数据时中文乱码。

request乱码是指:浏览器向服务器提交的请求参数中存在中文,服务器接收到中文数据时乱码。

其实,不管是request和response乱码,都是由于服务器和浏览器(客户端)两方使用的字符集编码不一致导致。

二、解决乱码的方法:
①、服务器向浏览器响应数据有两种方式:字节流和字符流,所以要解决中文乱码问题就需要分类处理:
a) 使用字节流向浏览器响应数据
在这里插入图片描述

这个时候产生乱码的原因是:中文转成字节数组时使用的是系统默认的字符集,可能和浏览器打开时采用的字符集不一致,那么就会有乱码的可能。
要解决这类乱码问题,只需要将服务器中文转成字节数组时采用的字符集和浏览器默认打开时的字符集一致即可,如:
编码解码乱码介绍以及乱码的解决方法_第3张图片

b) 使用字符流向浏览器响应数据
在这里插入图片描述

产生乱码的原因:字符流是有缓冲区的,response的设计默认的缓冲区的的字符集是ISO-8859-1,不支持中文。
解的方式是:设置缓冲区的字符集和浏览器默认打开时的字符集一致即可。
编码解码乱码介绍以及乱码的解决方法_第4张图片

②、浏览器向服务器提交数据时,根据请求方式的不同,中文乱码的处理也不尽相同。下面我们来分类说明:
a)、POST请求
产生乱码的原因:POST方式提交的数据是在请求体中,request对象接收到数据之后,放入request缓冲区,而request缓冲区的默认字符集是ISO-8859-1,不支持中文。
解决的办法是:修改request缓冲区的字符集。
在这里插入图片描述
b)、GET请求
产生乱码的原因:GET请求时参数直接写在地址栏,那么浏览器就会对这组数据进行依次URL编码。
解决的办法是:将存储到request缓冲区的中文数据先以ISO-8859-1的字符集获取,然后进行UTF-8方式解码
在这里插入图片描述

Get请求的编解码

URL组成
编码解码乱码介绍以及乱码的解决方法_第5张图片
URL在浏览器端的编码
浏览器对 PathInfo 和 QueryString 的编码是不一样的,不同浏览器对 PathInfo 也可能不一样,这就对服务器的解码造成很大的困难。

URL在服务器端的编码
URL 的 URI 部分进行解码的字符集是在 connector 的 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。

QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1,要使用 ContentType 中定义的编码就要设置 connector 的 中的 useBodyEncodingForURI 设置为 true。

HTTP Header 的编解码
当客户端发起一个 HTTP 请求除了上面的 URL 外还可能会在 Header 中传递其它参数如 Cookie、redirectPath 等,这些用户设置的值很可能也会存在编码问题,Tomcat 对它们又是怎么解码的呢?

对 Header 中的项进行解码也是在调用 request.getHeader 是进行的,如果请求的 Header 项没有解码则调用 MessageBytes 的 toString 方法,这个方法将从 byte 到 char 的转化使用的默认编码也是 ISO-8859-1。

而我们也不能设置 Header 的其它解码格式,所以如果你设置 Header 中有非 ASCII 字符解码肯定会有乱码。我们在添加 Header 时也是同样的道理,不要在 Header 中传递非 ASCII 字符,如果一定要传递的话,我们可以先将这些字符用 org.apache.catalina.util.URLEncoder 编码然后再添加到 Header 中,这样在浏览器到服务器的传递过程中就不会丢失信息了,如果我们要访问这些项时再按照相应的字符集解码就好了。

POST 表单的编解码

当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到服务器端,在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST 表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过 request.setCharacterEncoding(charset) 来设置。

另外针对 multipart/form-data 类型的参数,也就是上传的文件编码同样也是使用 ContentType 定义的字符集编码,值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加到 parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。

HTTP BODY 的编解码
当用户请求的资源已经成功获取后,这些内容将通过 Response 返回给客户端浏览器,这个过程先要经过编码再到浏览器进行解码。这个过程的编解码字符集可以通过 response.setCharacterEncoding 来设置,它将会覆盖 request.getCharacterEncoding 的值,并且通过 Header 的 Content-Type 返回客户端,浏览器接受到返回的 socket 流时将通过 Content-Type 的 charset 来解码,如果返回的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将根据 Html 的 中的 charset 来解码。如果也没有定义的话,那么浏览器将使用默认的编码来解码。

其它需要编码的地方
除了 URL 和参数编码问题外,在服务端还有很多地方可能存在编码,如可能需要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。

xml 文件可以通过设置头来制定编码格式

  

Velocity 模版设置编码格式:

 services.VelocityService.input.encoding=UTF-8 

JSP 设置编码格式:

 <%@page contentType="text/html; charset=UTF-8"%> 

访问数据库都是通过客户端 JDBC 驱动来完成,用 JDBC 来存取数据要和数据的内置编码保持一致,可以通过设置 JDBC URL 来制定如 MySQL:

url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"。

常见问题分析

在了解了 Java Web 中可能需要编码的地方后,下面看一下,当我们碰到一些乱码时,应该怎么处理这些问题?出现乱码问题唯一的原因都是在 char 到 byte 或 byte 到 char 转换中编码和解码的字符集不一致导致的,由于往往一次操作涉及到多次编解码,所以出现乱码时很难查找到底是哪个环节出现了问题,下面就几种常见的现象进行分析。

  1. 中文变成了看不懂的字符

    例如,字符串“淘!我喜欢!”变成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”编码过程如下图所示

    编码解码乱码介绍以及乱码的解决方法_第6张图片
    字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码,而且是一个汉字字符变成两个乱码字符。

  2. 一个汉字变成一个问号

    例如,字符串“淘!我喜欢!”变成了“??????”编码过程如下图所示

    编码解码乱码介绍以及乱码的解决方法_第7张图片
    将中文和中文符号经过不支持中文的 ISO-8859-1 编码后,所有字符变成了“?”,这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示,这也就是通常所说的“黑洞”,所有 ISO-8859-1 不认识的字符都变成了“?”。

  3. 一个汉字变成两个问号

    例如,字符串“淘!我喜欢!”变成了“????????????”编码过程如下图所示

    编码解码乱码介绍以及乱码的解决方法_第8张图片
    这种情况比较复杂,中文经过多次编码,但是其中有一次编码或者解码不对仍然会出现中文字符变成“?”现象,出现这种情况要仔细查看中间的编码环节,找出出现编码错误的地方。

  4. 一种不正常的正确编码

    还有一种情况是在我们通过 request.getParameter 获取参数值时,当我们直接调用

    String value = request.getParameter(name);

    会出现乱码,但是如果用下面的方式

     String value = String(request.getParameter(name).getBytes("ISO-8859-1"), "GBK"); 
    

    解析时取得的 value 会是正确的汉字字符,这种情况是怎么造成的呢?

    看下如所示:
    编码解码乱码介绍以及乱码的解决方法_第9张图片
    这种情况是这样的,ISO-8859-1 字符集的编码范围是 0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值“不变”。虽然中文字符在经过网络传输时,被错误地“拆”成了两个欧洲字符,但由于输出时也是用 ISO-8859-1,结果被“拆”开的中文字的两半又被合并在一起,从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字,但是还是不建议用这种不正常的方式取得参数值,因为这中间增加了一次额外的编码与解码,这种情况出现乱码时因为 Tomcat 的配置文件中 useBodyEncodingForURI 配置项没有设置为”true”,从而造成第一次解析式用 ISO-8859-1 来解析才造成乱码的。

    总结

    本文首先总结了几种常见编码格式的区别,然后介绍了支持中文的几种编码格式,并比较了它们的使用场景。接着介绍了 Java 那些地方会涉及到编码问题,已经 Java 中如何对编码的支持。并以网络 I/O 为例重点介绍了 HTTP 请求中的存在编码的地方,以及 Tomcat 对 HTTP 协议的解析,最后分析了我们平常遇到的乱码问题出现的原因。

    综上所述,要解决中文问题,首先要搞清楚哪些地方会引起字符到字节的编码以及字节到字符的解码,最常见的地方就是读取会存储数据到磁盘,或者数据要经过网络传输。然后针对这些地方搞清楚操作这些数据的框架的或系统是如何控制编码的,正确设置编码格式,避免使用软件默认的或者是操作系统平台默认的编码格式。

你可能感兴趣的:(java)