`
lingqi1818
  • 浏览: 248742 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

字符编码出现乱码原因追寻

阅读更多
这几天做一个项目,基本上前台都是通过AJAX请求过来的,前端设计师用了雅虎的yui框架来封装JS。由于我们的系统一直采用GBK字符集编码,但是前端yui却只能通过utf-8编码把数据传过来,因为没有权限去修改框架级别的代码,所以只好跟ui约定好,在传送的数据中加一个_inut_charest=utf-8的参数,然后我在程序里恶心的硬编码进行convert。代码如下:

return new String(value.getBytes("GBK"),_inut_charest);


很顺利的就调试通过了,只是觉得有点恶心,不便以后维护。

但是下午突然出现一个奇怪的问题,如果参数里的中文是偶数个的话解码没有问题,但是单数的个的话就会出现乱码。比如汉字“你”就会成为“浣?”。
在网上查询答案,五花八门,大多数都没讲解原理,而且不太正确。在经过一番研究后终于找到了原因。

首先在这里简单介绍几种常见的字符集编码
ASCII编码采用单字节编码
GBK,GB18030,GB2312中文双字节编码
UTF-16双字节unicode编码
UTF-8变长多字节unicode编码,其中实践证明汉字为3个字节编码
IS08859系列,单字节编码

如果大家想详细了解各种编码的特点和产生的原因,请另外寻找资料,这里不做敷述。


下面我们来看一段代码:

import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;

/**
 * 类TestEncode.java的实现描述:测试转码类
 * 
 * @author ke.chenk 2009-3-13 下午09:01:34
 * @mail lingqi1818@msn.com
 */
public class TestEncode {

    /**
     * 本实例在中文windows下运行,故默认字符集为GBK
     * 
     * @param args
     */
    public static void main(String[] args) {
        testSingle();
        testDouble();
    }

    /**
     * 测试单字节编码(将GBK编码以UTF-8方式读取,然后再转为GBK)
     */
    private static void testSingle() {
        String s1 = encodeCovert("UTF-8", "GBK", "你");
        System.out.println(s1);
        printByteArray(s1.getBytes());
        System.out.println();
        String s2 = encodeCovert("GBK", "UTF-8", s1);
        System.out.println(s2);
        printByteArray(s2.getBytes());
    }

    /**
     * 测试双字节编码(将GBK编码以UTF-8方式读取,然后再转为GBK)
     */
    private static void testDouble() {
        String s1 = encodeCovert("UTF-8", "GBK", "你好");
        System.out.println(s1);
        printByteArray(s1.getBytes());
        System.out.println();
        String s2 = encodeCovert("GBK", "UTF-8", s1);
        System.out.println(s2);
        printByteArray(s2.getBytes());
    }

    private static String encodeCovert(String srcCharset, String desCharset, String value) {
        if (value == null) {
            return null;
        }
        try {
            if ("".equals(srcCharset) || srcCharset == null) {
                return new String(value.getBytes(), desCharset);
            }
            if ("".equals(desCharset) || desCharset == null) {
                return new String(value.getBytes());
            }
            return new String(value.getBytes(srcCharset), desCharset);
        } catch (UnsupportedEncodingException e) {
            System.out.println(MessageFormat.format(
                                                    "convert encode fail srcCharset is {0} , desCharset is {1} , value is {2}",
                                                    srcCharset, desCharset, value));
            return value;
        }
    }

    private static void printByteArray(byte[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + ",");
        }
    }

}



运行结果如下:

浣?
-28,-67,63,
??
63,63,浣犲ソ
-28,-67,-96,-27,-91,-67,
你好
-60,-29,-70,-61,

由此可见,当我们的字符以较少字节的编码方式,被错误的读取成较大字节的编码,然后在转码成较少字节的编码方式后就会丢失数据。
比如汉字“你”,正常的十进制UTF-8,byte数组应该为[-28,-67,-96],但是进行GBK编码的时候,由于GBK是2字节方式编码,所以-28,-67构成了汉字“浣”,-96找不到对应编码方式,于是就加上了?的ASCII码63.但是汉字“你好”转成UTF-8刚好为-28,-67,-96,-27,-91,-67,是偶数个,并且2个2个的字符刚好在GBK中有对应编码,所以欺骗了编码规则,数据没有被替换,这样才正常解析回了UTF-8编码


结论:
1.任何的乱码产生都是有原因可以查询的。
2.只要以正确的方式读取原来的数据,再进行编码是不会产生丢失数据或者乱码的情况的。
3.GBK->UTF-8->GBK这个过程如果是单个汉字100%出问题,其他情况也可以类推。

解决方案:
1.整个应用统一编码格式就不会出现这个问题。
2.对参数以URLENCODING方式进行传输,但是这样在传输的时候会增加数据量。
3.在我们这个CASE中,前端直接以GBK方式传输数据,不要转为UTF-8


=============================================================

补充:
搞了半天,原来问题出在我们自己这里,看一段代码:


前台代码:
<!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=GBK">
<title>Insert title here</title>
</head>
<body>
<input type="text" id="testinput"/>
<input type="button" onclick="test();" value="提交"/>
</body>
</html>
<script language="JavaScript" type="text/javascript">
function test(){
//创建浏览器兼容的XMLHttpRequest对象
var xmlhttp;
try{
	xmlhttp= new ActiveXObject('Msxml2.XMLHTTP');
}catch(e){
	try{
		xmlhttp= new ActiveXObject('Microsoft.XMLHTTP');
	}catch(e){
		try{
			xmlhttp= new XMLHttpRequest();
		}catch(e){}
	}
}
//定义XMLHttpRequest对象的事件处理程序
xmlhttp.overrideMimeType("text/html;charset=UTF-8");
xmlhttp.onreadystatechange=function(){
	if(xmlhttp.readyState==4){
		if(xmlhttp.status==200){
			alert(xmlhttp.responseText);
		}else{
			alert(xmlhttp.status);
		}
	}
}
//创建一个连接
xmlhttp.open("post","/web/servlet/TestServlet");
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); 
//发送请求
xmlhttp.send("myparam="+document.getElementById("testinput").value);
}
</script>

后台代码:

response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        request.setCharacterEncoding("UTF-8");
        System.out.println(request.getCharacterEncoding());
        String s = request.getParameter("myparam");
        System.out.println("pure s ->"+s);
        System.out.println(new String(s.getBytes("UTF-8"),"GBK"));
        byte[] temp = s.getBytes("UTF-8");
        for (int i = 0; i < temp.length; i++) {
        System.out.println(temp[i]+",");
        }
        System.out.println("===============================");
        String[] charsets = { "GBK", "GB2312", "8859_1", "UTF-8", "UTF-16" };
        for (int i = 0; i < charsets.length; i++) {
            for (int j = 0; j < charsets.length; j++) {
                if (i != j) {
                    String ss = new String(s.getBytes(charsets[i]), charsets[j]);
                    System.out.println("i="+i+",j="+j+"---"+ss);
                }
            }
        }



打印结果如下:
UTF-8
pure s ->你
浣?
-28,
-67,
-96,
===============================
i=0,j=1---你
i=0,j=2---??
i=0,j=3---??
i=0,j=4---?
i=1,j=0---你
i=1,j=2---??
i=1,j=3---??
i=1,j=4---?
i=2,j=0---?
i=2,j=1---?
i=2,j=3---?
i=2,j=4---?
i=3,j=0---浣?
i=3,j=1---浣?
i=3,j=2---???
i=3,j=4---?
i=4,j=0---??O`
i=4,j=1---??O`
i=4,j=2---??O`
i=4,j=3---??O`

可见,前面我们的分析原理的思路是正确的,但是乱码产生的原因不是js转码产生的,其实js会正确的将页面的GBK编码转换成为UTF-8,然后我们的框架会根据_input_charset=UTF-8来设置request.setCharacterEncoding("UTF-8");这样当我们从request.getParameter("param")的时候已经是正确的中文了,由于一开始我们没有加_input_charset=UTF-8这个参数,所以一直采用硬编码进行转换,结果当编码正确的时候我们又重新进行了转换,才有了GBK->UTF-8->GBK的步骤,乱码也就随之产生了。
2
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics