我相信做javaEE开发的程序猿们,不管是经验丰富,还是初生牛犊,都肯定在开发的过程中,遇到过乱码的问题。我本人也深受其害。而且乱码问题防不胜防
,让人十分头疼。
下面我就准备就乱码问题。说一说我自己的心得和体会。
在说之前首先。我要明确几个概念。
1,首先明确一点,记事本的默认编码是ASNI,这个ASNI是什么意思呢,它的意思是说如果是在中国,默认的编码就是GBK,如果是在中欧的话,那可能就是ISO-8859-1
2, 很多数据库的编码类型中并看不到ISO-8859-1这个编码,其实是有的,只是叫了另外一个名字latin1
3,编码意指按照一定规律将字符转化为二进制字节码
4,解码意指将二进制字节按照一定规律解读成字符
5,ISO-8859-1按照单字节去编码字符,也是就8位。所以就算它8位都用上可以编码的字符也就255个。但正因为它是单字节编码,所以其他字符集编码形成的二进制字节码,
如果用ISO-8859-1去解码的话,并不会造成字节丢失。
明确了这些之后,不妨跟着我一起做个小实验。
首先声明,实验的地点是在中国,软件环境是jdk1.7 32、tomcat6.0 32、firebox、 ie8
实验的数据:
序号 字符 GBK编码生成的二进制字节(以下用十六进制表示) UTF8生成的二进制字节(以下用十六进制表示)
1 我 CED2 E68891
2 我的 CED2B5C4 E68891E79A84
3 我1 CED231 E6889131
向服务端发送请求的方式:
1,超链接 这种请求都为get请求
2,form表单的get请求
3,form表单的post请求
4,ajax的get请求(即xmlhttprequest发出的get请求)
5,ajax的post请求(即xmlhttprequest发出的post请求)
咱们就这几种情况编写jsp与servlet如下。
从这个包的内容上可以看出,参数被浏览器按照GBK进行了编码,并直接拼在了url后面。至于说%是怎么会事,这个是URLEcode干的活,对我们的编码解码没什么影响,
这里就不深究了。
我在做这个实验的时候,我就很好奇,为什么浏览器就默认地将参数按照GBK进行了转码呢,后来我找了下,发现如果我要是将Encoding.jsp中的contentType改为contentType="text/html; charset=UTF-8"这个时候页面上的文字编码就变成了UTF-8。而且点击超链接时,传给服务器的参数也按照UTF-8进行了编码,并且这个时候地址栏也将按明文进行显示。效果如下图:
好,咱们将jsp上的contentType="text/html; charset=UTF-8"改回contentType="text/html; charset=GB2312"继续实验。
这个时候浏览器发送给服务端的参数将按照GBK进行编码,而且请求头中并没有指定contentType,意思也就是说浏览器没有指定服务器端该用什么编码来解码。学过web开
发的同学应该知道,当浏览器没有指定服务器用什么编码来解码的时候,tomcat6和tomcat7的做法是用ISO-8859-1来默认解码,很显然ISO-8859-1是不能识别出GBK编码出的二进制字节码的,所以这个时候服务器端将会出现乱码现象。我为了测试,在服务端写了个to16Byte(String name)的方法,目的就是根据浏览器指定的服务端解码的类型,将以 request.getParameter("str");这种形式接收到的字符还原为二进制字节码。
所以这个时候是可以通过自己还原出的二进制字节码,重新去解码得到不乱码的字符的。
对参数进行编码,而且默认情况下,浏览器不会指定服务端用什么编码进行解码,服务端这个时候会用ISO-8859-1进行解码,解码肯定是不能成功的,所以会出现乱码情况。
不过情况还好,ISO-8859-1解码浏览器传过来的二进制编码的时候,并不会修改二进制字节码,传输的信息并不会丢失。
再看ajax,我在写ajax的时候,不管是get请求,还是post请求,我都指定了服务器端的解码类型为gbk。
我们定义的contentType,服务器也正常解析出了浏览器给指定的解码类型gbk,然后后台就去用这个gbk解码浏览器传输过来的UTF-8编码后得到的二进制字节码,结果呢,
出现了乱码。而且这种乱码是不可逆的。如上图UTF将"我"这个字符编码为E68891,而服务器端用GBK解码之后得到的字符的二进制字节码为E6883f。最终显示的字符为"鎴?"
这里面可以看出来,UTF-8以三个字节去编码一个"我"字,而GBK用两个字节来编码一个汉字。所以GBK去解码E68891的时候,是两个字节两个字节的解码的,而E68891只有
三个字节,前两个字节被gbk解码成了一个汉字,而后面的一个字节由于不能识别,就被gbk用3f替换掉了。所以这种时候,发生了字节丢失,所以这种情况下的乱码,不可
恢复。那有没有什么好的解决办法呢,其实我写到这里的时候,就已经想出来了,三个解决办法,第一个就是,直接在发送请求的时候,指定服务端的以UTF-8来解码,第二
个就是,在发送请求的时候不指定服务端以什么来解码了,直接让服务端以ISO-8859-1来解码,这样字节不会丢失,自己可以在服务端恢复。第三种办法,就是在浏览器端,
不指定服务器端以什么来解码,而是在服务端里,在你获取这个参数之前request.setCharacterEncoding("UTF-8");设置一下解码方式为utf-8就可以了。这三种方法经测试都有效。
字节码,并且浏览器自己指定了服务器端必须得用UTF-8这个编码来解码。所以ajax的post请求在默认情况下,一定不会乱码。
经测试在ie8中超链接依然会根据jsp中定义contentType来指定编码,但是不管是gbk来编码,还是UTF-8来编码,浏览器地址栏都会显示为明文。
经测试在ie8中form发送的get请求会根据jsp中定义contentType来指定编码,但是不管是gbk来编码,还是utf-8编码,浏览器地址栏都不会显示明文。
经测试在ie8中form发送的post请求会根据jsp中定义contentType来指定编码。
经测试在ie8中ajax发送的post请求一样是用utf-8编码,但是在ie8中可以指定用什么来解码,而不是像firebox那样浏览器自己指定用utf-8来解码。
还有一点就是如果我用ajax的get请求以UTF-8编码的二进制字节码,传给浏览器一个字符串"我的",并指定服务器用gbk去解码,这个时候由于"我的"被UTF-8编码成E68891E79A84
一共是6个字节,这和GBK用两个字节编码汉字不谋而合,所以这个时候如果用GBK去解码这个二进制字节码的时候,由于位数和GBK相符合,不会造成字节丢失的现象。所以这个时
候是可以手动还原出二进制的字节码,然后解码成字符的。
,让人十分头疼。
下面我就准备就乱码问题。说一说我自己的心得和体会。
在说之前首先。我要明确几个概念。
1,首先明确一点,记事本的默认编码是ASNI,这个ASNI是什么意思呢,它的意思是说如果是在中国,默认的编码就是GBK,如果是在中欧的话,那可能就是ISO-8859-1
2, 很多数据库的编码类型中并看不到ISO-8859-1这个编码,其实是有的,只是叫了另外一个名字latin1
3,编码意指按照一定规律将字符转化为二进制字节码
4,解码意指将二进制字节按照一定规律解读成字符
5,ISO-8859-1按照单字节去编码字符,也是就8位。所以就算它8位都用上可以编码的字符也就255个。但正因为它是单字节编码,所以其他字符集编码形成的二进制字节码,
如果用ISO-8859-1去解码的话,并不会造成字节丢失。
明确了这些之后,不妨跟着我一起做个小实验。
首先声明,实验的地点是在中国,软件环境是jdk1.7 32、tomcat6.0 32、firebox、 ie8
实验的数据:
序号 字符 GBK编码生成的二进制字节(以下用十六进制表示) UTF8生成的二进制字节(以下用十六进制表示)
1 我 CED2 E68891
2 我的 CED2B5C4 E68891E79A84
3 我1 CED231 E6889131
向服务端发送请求的方式:
1,超链接 这种请求都为get请求
2,form表单的get请求
3,form表单的post请求
4,ajax的get请求(即xmlhttprequest发出的get请求)
5,ajax的post请求(即xmlhttprequest发出的post请求)
咱们就这几种情况编写jsp与servlet如下。
1,Encoding.jsp
<%@ page language="java" contentType="text/html;charset=GB2312"
pageEncoding="GB2312"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312">
<title>Insert title here</title>
<base href="<%=(request.getContextPath()+"/")%>"/>
<script>
function createXMLHTTPRequest() {
//1.创建XMLHttpRequest对象
//这是XMLHttpReuquest对象无部使用中最复杂的一步
//需要针对IE和其他类型的浏览器建立这个对象的不同方式写不同的代码
var xmlHttpRequest;
if (window.XMLHttpRequest) {
//针对FireFox,Mozillar,Opera,Safari,IE7,IE8
xmlHttpRequest = new XMLHttpRequest();
//针对某些特定版本的mozillar浏览器的BUG进行修正
if (xmlHttpRequest.overrideMimeType) {
xmlHttpRequest.overrideMimeType("text/xml");
}
} else if (window.ActiveXObject) {
//针对IE6,IE5.5,IE5
//两个可以用于创建XMLHTTPRequest对象的控件名称,保存在一个js的数组中
//排在前面的版本较新
var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];
for ( var i = 0; i < activexName.length; i++) {
try {
//取出一个控件名进行创建,如果创建成功就终止循环
//如果创建失败,回抛出异常,然后可以继续循环,继续尝试创建
xmlHttpRequest = new ActiveXObject(activexName[i]);
if(xmlHttpRequest){
break;
}
} catch (e) {
}
}
}
return xmlHttpRequest;
}
function get(str){
var req = createXMLHTTPRequest();
if(req){
req.open("GET", "EncodingServlet.do?str="+str, true);
req.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=gbk;");
req.onreadystatechange = function(){
if(req.readyState == 4){
if(req.status == 200){
alert("success");
}else{
alert("error");
}
}
}
req.send(null);
}
}
function post(str){
var req = createXMLHTTPRequest();
if(req){
req.open("POST", "EncodingServlet.do", true);
req.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=gbk;");
req.send("str="+str);
req.onreadystatechange = function(){
if(req.readyState == 4){
if(req.status == 200){
alert("success");
}else{
alert("error");
}
}
}
}
}
</script>
</head>
<body>
<%
String str = "我";
%>
<a href="EncodingServlet.do?str=<%=str %>">超链接----get传递<%=str %></a><br>
form---get传递<%=str %>:
<form action="EncodingServlet.do" method="get">
<input name="str" value="<%=str %>" type="text"/>
<input value="提交" type="submit"/>
</form>
<br>
form---post传递<%=str %>:
<form action="EncodingServlet.do" method="post">
<input name="str" value="<%=str %>" type="text"/>
<input value="提交" type="submit"/>
</form>
<br>
<a href="javascript:void(0)" οnclick="get('<%=str%>')">ajax---get传递<%=str %></a><br>
<a href="javascript:void(0)" οnclick="post('<%=str%>')">ajax---post传递<%=str %></a>
</body>
</html>
2, EncodingServlet
package com.web.test;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class TestServlet
*/
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected PrintWriter out;
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//获取参数
String str = request.getParameter("str");
//获取客户端指定的解码方式
String encoding = request.getCharacterEncoding();
System.out.println("解码字符集:"+encoding);
System.out.println(str+"-------"+to16Byte(str,encoding));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
//根据浏览器指定的服务端解码的类型,将以 request.getParameter("str");这种形式接收到的字符还原为二进制字节码
public static String to16Byte(String str,String decode) throws Exception{
if(decode == null)decode="ISO-8859-1";
byte[] b = str.getBytes(decode);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; i++) {
int high = (b[i] >> 4) & 0xf;
int low = b[i] & 0xf;
sb.append(Character.forDigit(high, 16)).append(Character.forDigit(low, 16));
}
return sb.toString();
}
}
3, 在web.xml中配置如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>EncodingServlet</servlet-name>
<servlet-class>com.web.test.EncodingServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EncodingServlet</servlet-name>
<url-pattern>/EncodingServlet.do</url-pattern>
</servlet-mapping>
</web-app>
在firebox中Encoding页面如下:
页面的文字编码:
咱们点击超链接发送一个get请求给服务器,这个时候抓的包如下:
从这个包的内容上可以看出,参数被浏览器按照GBK进行了编码,并直接拼在了url后面。至于说%是怎么会事,这个是URLEcode干的活,对我们的编码解码没什么影响,
这里就不深究了。
我在做这个实验的时候,我就很好奇,为什么浏览器就默认地将参数按照GBK进行了转码呢,后来我找了下,发现如果我要是将Encoding.jsp中的contentType改为contentType="text/html; charset=UTF-8"这个时候页面上的文字编码就变成了UTF-8。而且点击超链接时,传给服务器的参数也按照UTF-8进行了编码,并且这个时候地址栏也将按明文进行显示。效果如下图:
这个时候页面上的文字编码也将由gbk变为UTF-8:
这个时候点击超链接之后的效果:
好,咱们将jsp上的contentType="text/html; charset=UTF-8"改回contentType="text/html; charset=GB2312"继续实验。
这个时候浏览器发送给服务端的参数将按照GBK进行编码,而且请求头中并没有指定contentType,意思也就是说浏览器没有指定服务器端该用什么编码来解码。学过web开
发的同学应该知道,当浏览器没有指定服务器用什么编码来解码的时候,tomcat6和tomcat7的做法是用ISO-8859-1来默认解码,很显然ISO-8859-1是不能识别出GBK编码出的二进制字节码的,所以这个时候服务器端将会出现乱码现象。我为了测试,在服务端写了个to16Byte(String name)的方法,目的就是根据浏览器指定的服务端解码的类型,将以 request.getParameter("str");这种形式接收到的字符还原为二进制字节码。
这个时候服务端输出如下:
所以这个时候是可以通过自己还原出的二进制字节码,重新去解码得到不乱码的字符的。
form表单的get请求和超链接发出的get请求几乎一模一样,这里就不另作解释了。(在utf-8编码下在地址栏仍然是明文显示。)仅仅展示下form表单get请求抓到的数据包。
根据抓到的包而言,很显然数据也是用GBK进行编码的,也没有在请求头中指定服务端的解码类型,所以数据到了服务端,依然会用ISO-8859-1来解码,所以这个时候依然会乱码。
再看看form表单post请求的数据包:
不过与get请求不同的是,post请求不会把参数拼在url上,也不会在地址栏显示。而是整个放在了消息体里,跟在请求头之后,并按照键值对的形式用&进行拼接。
这里只有一个参数,所以看起来不是很明显。
在服务器端输出:
很显然post请求也和get请求一样使用GBK来编码传输的参数,然后在服务器端也是用ISO-8859-1来编码。
综上这里先得出结论,在firebox和tomcat6.0的基础上,原生的form表单发送的get请求和post请求,以及超链接发送的get请求,都会根据jsp上的contentType指定的编码对参数进行编码,而且默认情况下,浏览器不会指定服务端用什么编码进行解码,服务端这个时候会用ISO-8859-1进行解码,解码肯定是不能成功的,所以会出现乱码情况。
不过情况还好,ISO-8859-1解码浏览器传过来的二进制编码的时候,并不会修改二进制字节码,传输的信息并不会丢失。
再看ajax,我在写ajax的时候,不管是get请求,还是post请求,我都指定了服务器端的解码类型为gbk。
服务器端输出:
我们定义的contentType,服务器也正常解析出了浏览器给指定的解码类型gbk,然后后台就去用这个gbk解码浏览器传输过来的UTF-8编码后得到的二进制字节码,结果呢,
出现了乱码。而且这种乱码是不可逆的。如上图UTF将"我"这个字符编码为E68891,而服务器端用GBK解码之后得到的字符的二进制字节码为E6883f。最终显示的字符为"鎴?"
这里面可以看出来,UTF-8以三个字节去编码一个"我"字,而GBK用两个字节来编码一个汉字。所以GBK去解码E68891的时候,是两个字节两个字节的解码的,而E68891只有
三个字节,前两个字节被gbk解码成了一个汉字,而后面的一个字节由于不能识别,就被gbk用3f替换掉了。所以这种时候,发生了字节丢失,所以这种情况下的乱码,不可
恢复。那有没有什么好的解决办法呢,其实我写到这里的时候,就已经想出来了,三个解决办法,第一个就是,直接在发送请求的时候,指定服务端的以UTF-8来解码,第二
个就是,在发送请求的时候不指定服务端以什么来解码了,直接让服务端以ISO-8859-1来解码,这样字节不会丢失,自己可以在服务端恢复。第三种办法,就是在浏览器端,
不指定服务器端以什么来解码,而是在服务端里,在你获取这个参数之前request.setCharacterEncoding("UTF-8");设置一下解码方式为utf-8就可以了。这三种方法经测试都有效。
服务器端输出:
字节码,并且浏览器自己指定了服务器端必须得用UTF-8这个编码来解码。所以ajax的post请求在默认情况下,一定不会乱码。
经测试在ie8中超链接依然会根据jsp中定义contentType来指定编码,但是不管是gbk来编码,还是UTF-8来编码,浏览器地址栏都会显示为明文。
经测试在ie8中form发送的get请求会根据jsp中定义contentType来指定编码,但是不管是gbk来编码,还是utf-8编码,浏览器地址栏都不会显示明文。
经测试在ie8中form发送的post请求会根据jsp中定义contentType来指定编码。
经测试在ie8中ajax发送的post请求一样是用utf-8编码,但是在ie8中可以指定用什么来解码,而不是像firebox那样浏览器自己指定用utf-8来解码。
还有一点就是如果我用ajax的get请求以UTF-8编码的二进制字节码,传给浏览器一个字符串"我的",并指定服务器用gbk去解码,这个时候由于"我的"被UTF-8编码成E68891E79A84
一共是6个字节,这和GBK用两个字节编码汉字不谋而合,所以这个时候如果用GBK去解码这个二进制字节码的时候,由于位数和GBK相符合,不会造成字节丢失的现象。所以这个时
候是可以手动还原出二进制的字节码,然后解码成字符的。