main(String[] args) throws UnsupportedEncodingException { [] gbkData = {(byte) 0xc4, (byte) 0xe3, (byte) 0xba, (byte) 0xc3}; //转换到Unicode String tmp = new String(gbkData, "GBK"); [] big5Data = tmp.getBytes("Big5"); //后续操作…… }
4.编码丢失问题上面已经解释了,JSP框架采用ISO-8859-1字符集来解码的原因。先用一个例子来模拟这个还原过程,代码如下
public class Test { main(String[] args) throws UnsupportedEncodingException { [] data = {(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd}; //打印原始数据 showBytes(data); //JSP框架假设它是ISO-8859-1的编码,生成一个String对象 String tmp = new String(data, "ISO-8859-1"); //**************JSP框架部分结束******************** //开发者拿到后打印它发现是6个欧洲字符,而不是预期的"你好" System.out.println(" ISO解码的结果:" + tmp); [] utfData = tmp.getBytes("ISO-8859-1"); //打印还原的数据 showBytes(utfData); //开发者知道它是UTF-8编码的,因此用UTF-8的代码页,重新构造String对象 String result = new String(utfData, "UTF-8"); //再打印,正确了! System.out.println(" UTF-8解码的结果:" + result); } showBytes(byte[] data) { for (byte b : data) System.out.printf("0x%x ", b); System.out.println(); } }
运行结果如下,第一次输出是不正确的,因为解码规则不对,也查错了代码页,得到的是错误的Unicode。然后发现通过错误的Unicode反查ISO-8859-1代码页还能完美的还原数据。
然后我们尝试把ISO-8859-1替换为ASCII,结果就会变成这样子
这是因为,ASCII虽然也是每字节对应一个字符,但是它只对0~127这个空间进行了编码,也就是说每个字节的最大值只能为0x7F,而上面的6个字节全部都大于这个数值,因此在ASCII的代码页中是找不到这6个字节的,于是Java就搞了一个缺省值。我用如下的代码测试发现,当通过编码数据在代码页中查不到对应的Unicode时,就返回缺省值\ufffd(对应图中第一种问号),反过来,当通过Unicode在代码页中查不到对应的编码数据时,就返回缺省值0x3f(ASCII,对应图中第二种问号)。由此,这个输出结果也就可以解释清楚了。
main(String[] args) throws IOException { [] data = {(byte) 0x80}; showUnicode(new String(data, "UTF-8")); showUnicode(new String(data, "GBK")); showUnicode(new String(data, "Big5")); //输出结果全为0x3f String str = "\uccdd"; showBytes(str, "GBK"); showBytes(str, "BIG5"); showBytes(str, "ISO-8859-1"); }
5.Java源文件的编码问题这就是开头所提到的那个问题,把问题描述一下先。就如下这么一小段代码,源文件使用UTF-8编码保存。(注意别用Windows的记事本,因为它会在UTF-8文件最前面加入一个3字节的BOM头,而很多程序都不兼容这一点)
public class Test { main(String[] args) { System.out.println("中"); } }
然后在Windows中使用默认参数编译该文件(系统区域设置为简体中文,即默认使用GBK字符集解码),然后会得到如下错误
这不是重点,重点如果把“中”换成“中国”,编译就会成功,运行结果如下图。另外进一步可发现,中文字符个数为奇数时编译失败,偶数时通过。这是为什么呢?下面详细分析一下。
因为Java String内部使用的是Unicode,所以在编译的时候,编译器就会对我们的字符串字面量进行转码,从源文件的编码转换到Unicode(维基百科说用的是与UTF-8稍微有点不同的编码)。编译的时候我们没有指定encoding参数,所以编译器会默认以GBK方式去解码,对UTF-8和GBK有点了解的应该会知道,一般一个中文字符使用UTF-8编码需要3个字节,而GBK只需要2个字节,这就能解释为什么字符数的奇偶性会影响结果,因为如果2个字符,UTF-8编码占6个字节,以GBK方式来解码恰好能解码为3个字符,而如果是1个字符,就会多出一个无法映射的字节,就是图中问号的地方。