《Java中文乱码——1、Java编解码基础》讲解了Java编解码的细节,本篇幅具体来解释为什么乱码后中文会变成问号“?”。先看一下下面这个例子。
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "GBK中文";
printCharArr(s.toCharArray());
byte[] bs = s.getBytes("ISO-8859-1");
printByteArr(bs);
String s2 = new String(bs, "GBK");
printCharArr(s2.toCharArray());
System.out.println(s2);
}
private void printByteArr(byte[] bs) {
StringBuilder sb = new StringBuilder();
for(byte b : bs) {
sb.append(toHex(b)).append(",");
}
System.out.println(sb.toString());
}
private void printCharArr(char[] charArr) {
StringBuilder sb = new StringBuilder();
for(char c : charArr) {
int high = (c & 0xff00) >> 8;
int low = c & 0xff;
sb.append(toHex(high)).append(",");
sb.append(toHex(low)).append(",");
}
System.out.println(sb.toString());
}
private String toHex(int intVal) {
String hex = Integer.toHexString(b);
if (hex.length() > 2) {
hex = hex.substring(hex.length() - 2);
}
return hex;
}
得到了如下输出
0,47,0,42,0,4b,4e,2d,65,87,
47,42,4b,3f,3f,
0,47,0,42,0,4b,0,3f,0,3f,
GBK??
注意,输出内容是16进制格式的。接着我来详细讲解一下过程。
字符串“GBK中文”在内存中是以utf-16(unicode)保存的。utf-16为定长编码,每个字符占2字节。如图所示,英文"G"值为0x0047,中文“中”值为0x4e2d。
bs=s.getBytes("ISO-8859-1")要求将字符串编码成ISO-8859-1,可以理解成将byte[]由utf-16转换成ISO-8859-1。 我们知道,ISO-8859-1也是定长编码,但每个字符只占1个字节。此时英文"G"在ISO-8859-1下有对应的编码,值为0x47。中文“中”在"ISO-8859-1"下没有对应的编码,于是系统默认用问号“?”的ascii值0x3f代替。可以看到,此时已经发生了信息丢失。
之后,new String(bs, "GBK")要求将bs理解成GBK编码的字符数组,再对数组进行解码,可以理解成将bs由GBK转换成utf-16。我们知道,GBK是变长编码,每个西文字符占1个字节,和ascii兼容,而每个中文占2个字节。对于前三个字符,0x47、0x42、0x4b,在GBK下属于西文的编码范围,因此1次读取1个字节,转换成UTF-16。对于后面2个字符0X3f、0X3f,程序并不知道它们之前是汉字“中”、“文”,只知道它们的编码是0X3f,在GBK下对应西文字符“?“。
于是,经过如上转换,”GBK中文“变成了”GBK??“。
那么,发现中文乱码出现"?"以后,是否可以通过转码来恢复呢?
答案是不能。因为在转换成ISO-8859-1 byte[]的过程中发生了信息丢失,已经无法恢复了