JavaWeb中文乱码问题
概述
在 JavaWeb 中通过 请求(
request
) 和 响应(response
) 来进行 数据传递 的过程中,有一个不可避免的问题,即传输的数据中有可能包含中文。
当传输的数据中包含中文时,往往会出现一个常见问题,即 中文乱码 问题,在这里详细解释一下出现中文乱码问题的原因以及解决的方法,服务器以 Tomcat为准
出现中文乱码问题的原因
所谓的 请求 和 响应,其实是数据的 流向 问题
- 从 浏览器 端发送数据到 服务器 端,称之为 请求(
request
) - 从 服务器 端发送数据到 浏览器 端,称之为 响应(
response
)
在浏览器和服务器之间传递数据时,存在一个 编码/解码 的过程
- 浏览器 -> 服务器:数据以浏览器规定的 字符集 对 待发送的数据 进行 编码,然后发送到服务器(请求);服务器接收到来自浏览器的数据,以自己(服务器)规定的 字符集 对该数据进行 解码
- 服务器 -> 浏览器:数据以服务器规定的 字符集 对 待发送的数据 进行 编码,然后发送到浏览器(响应);浏览器接收到来自服务器的数据,以自己(浏览器)规定的 字符集 对该数据进行 解码
常用字符集:ISO-8859-1
、UTF-8
、GBK
...
导致中文乱码原因:
- 编码 时使用的 字符集 与 解码 时使用的 字符集 不一致,而不同 字符集 对 中文 的编解码方式是 不一致 的
- 字符集 本身不包含 中文;例如
ISO-8859-1
,它本身不包含中文,如果用它对中文进行 编码 或者 解码,则会直接出现中文乱码
注意:
- 数据传输时,若数据中包含中文,且 编码 和 解码 所使用的 字符集 不一致,则出现中文乱码问题;这是由于不同的 字符集 对 中文 的编解码方式是 不一致的;例如:用
UTF-8
对 中文 进行编码,再以GBK
对其解码,会出现中文乱码问题 - 数据传输时,若数据中不包含中文(只有英文和数字),那么即使 编码 和 解码 所使用的 字符集 不一致,也不会出现乱码问题;这是由于不同的 字符集 对 英文和数字 的编解码方式是 一致的;例如:用
UTF-8
对 英文和数字 进行编码,再以GBK
对其解码,不会出现乱码问题
解决中文乱码问题的方案
- 使得 编码 与 解码 时所使用的 字符集 保持一致
- 使用支持中文的字符集对中文进行 编码 和 解码
请求时中文乱码问题
请求分为
GET
请求与POST
请求两种方式,它们提交数据的方式是不同的;GET
请求在 请求行(URI
) 中提交数据,POST
请求在 请求体 中提交数据
请求编码
所谓的请求编码,即浏览器发送数据给服务器之前,浏览器对数据进行的 编码
- 直接在浏览器地址栏中给定
URL
,这种请求方式一般会将 提交数据 以UTF-8
进行编码 - 通过页面上的 表单 或者 超链接 发送请求,则由当前页面的编码(
<meta charset="字符集">
)来决定 提交数据 的编码方式,例如:当前页面<meta charset="GBK">
,则 提交数据 以GBK
进行编码
GET 请求中文乱码问题
乱码原因:
- Tomcat8 之前的版本中,
URI
(请求行) 的 默认字符集 是ISO-8859-1
,即GET
请求中提交的数据会以ISO-8859-1
进行 解码,该字符集是不包含中文的,因此会出现中文乱码 - Tomcat8 之后,
URI
的 默认字符集 被修改为UTF-8
,即GET
请求中提交的数据会以UTF-8
进行 解码,此时只要浏览器提交的数据是以UTF-8
进行 编码 的(<meta charset="UTF-8">
),就不会出现中文乱码问题
Tomcat8 之前乱码解决方案:
- 前提:当前浏览器向服务器发送的数据是以
UTF-8
进行编码的(<meta charset="UTF-8">
) - 在
CATALINA_HOME/conf/server.xml
的<Connector>
标签中,设置URIEncoding="UTF-8"
,即将URI
的 默认字符集 修改为UTF-8
,该字符集是包含中文的
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"/>
- 只要调用了
request.getParameter()
方法获取参数,则获取到的参数一定是使用了ISO-8859-1
进行 解码 的 错误数据,可以使用getBytes("ISO-8859-1")
将当前参数以ISO-8859-1
转回byte[]
字节数组,再通过new String(byte[], "字符集")
使用正确的 字符集(UTF-8
) 来进行 解码 即可
String username = request.getParameter("username"); // 使用 ISO-8859-1 错误的解码了
byte[] bytes = username.getBytes("ISO-8859-1"); // 退回错误的解码,让字符串通过 ISO-8859-1 返回到字节数组,即还原字节数据
username = new String(bytes, "UTF-8"); // 重新使用正确的字符集(UTF-8)对字节数据进行解码
注意:
- 以
ISO-8859-1
中转UTF-8
数据是 安全的,即ISO-8859-1
错误解码,利用byte[] bytes = getBytes("ISO-8859-1")
还原字节数组,再以new String(bytes, "UTF-8")
重新解码数据是可行的 - 以
GBK
中转UTF-8
数据是 不安全的,即GBK
错误解码,利用byte[] bytes = getBytes("GBK")
还原字节数组,再以new String(bytes, "UTF-8")
重新解码数据是 不可行的
POST 请求中文乱码问题
乱码原因:
- Tomcat10 之前,对 请求体 的字符集没有特殊指定,则 请求体 是按照 Servlet规范 中指定的字符集进行 解码 的,而 Servlet规范 的默认字符集是
ISO-8859-1
,因此会出现中文乱码 - Tomcat10 之后,对 请求体 的字符集有了特殊指定,即指定为
UTF-8
,则 请求体 不再是按照 Servlet规范 中指定字符集进行 解码,而是以UTF-8
进行 解码,此时只要保证浏览器发送给服务器的数据是以UTF-8
进行 编码 的(<meta charset="UTF-8">
),就不会出现中文乱码问题
Tomcat10 之前乱码解决方案:
- 前提:当前浏览器向服务器发送的数据是以
UTF-8
进行编码的(<meta charset="UTF-8">
) - 在获取请求参数之前,手动设置 请求体 的字符集为
UTF-8
,即指定以UTF-8
对请求体的内容进行 解码,该字符集包含中文,用它对中文进行编码,且保证 编解码 的字符集一致,就不会出现中文乱码问题
request.setCharacterEncoding("UTF-8"); // 这行代码一定要在 获取请求参数之前,否则无效
String username = request.getParameter("username");
响应时中文乱码问题
响应编码
所谓的响应编码,即服务器发送数据给浏览器之前,服务器对数据进行的 编码
- 服务器通过
response.getWriter()
方法向浏览器发送数据
响应乱码原因
- 如果在
response.getWriter()
方法调用之前,不手动设置服务器的编码方式,则默认将 响应内容 以ISO-8859-1
进行编码,若响应内容中包含中文,则必然发生中文乱码(使用了不支持中文的字符集对中文进行编码)
PrintWriter out = response.getWriter();
out.print("你好"); // 必定乱码
- 在调用
response.getWriter()
方法之前,调用response.setCharacterEncoding("UTF-8")
方法来手动设置 响应内容 以UTF-8
进行编码,若响应内容中包含中文,在服务器中 编码 时不会乱码,但在发送给浏览器之后,浏览器是不知道服务器的编码字符集的,它可能默认会以GBK
来 解码,则页面中从服务器获取来的 中文内容 依旧会乱码(编解码不一致)
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print("你好"); // 服务器端不会乱码,客户端有可能乱码
响应乱码解决方案
- 在调用
response.getWriter()
方法之前,调用response.setHeader("Content-Type", "text/html;charset=UTF-8")
方法设置响应头,通知浏览器当前服务器编码的字符集;该方法执行之后,会 自动执行response.setCharacterEncoding("UTF-8")
,即服务器以UTF-8
对响应内容进行 编码,而浏览器 也会用UTF-8
进行 解码
response.setHeader("Content-Type", "text/html;charset=UTF-8"); // 这行代码一定要在 response.getWriter() 之前,否则无效
PrintWriter out = response.getWriter();
out.print("你好"); // 服务器端不会乱码,客户端也不会乱码
response.setContentType("text/html;charset=utf-8")
与response.setHeader("Content-Type", "text/html;charset=UTF-8")
效果一致,因此一般使用response.setContentType("text/html;charset=utf-8")
就可以了
response.setContentType("text/html;charset=utf-8"); // 这行代码一定要在 response.getWriter() 之前,否则无效
PrintWriter out = response.getWriter();
out.print("你好"); // 服务器端不会乱码,客户端也不会乱码