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

这几天做一个项目,基本上前台都是通过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的步骤,乱码也就随之产生了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值