字符编码问题之手动转码并不万能

  • 引言
相信大家在最开始接触web的时候,因为Tomcat服务器默认的URIEncoding是ISO8859-1。
应该产生过中文乱码问题,某些前辈就会告诉你,可以手动转码。

new String(str.getBytes("IOS8859-1"),"gbk");

这句话大家应该不陌生,如果页面编码方式是gbk,Tomcat URIEncoding是ISO8859-1。
这样编码再解码,看着确实合理,而且能解决问题。
但是其实手动转码局限性是比较大的。


  • 基本概念介绍
首先你应该知道编码是干什么,编码就是把字符按照规定的字符集转换成 0 1 的字节流形式.
解码,是把相应的0 1字节流按照规定字符集转换成相应字符的形式。

然后你应该知道,Java中的字符都是按照Unicode字符集进行编码的。 
也就是说,JVM管理的内存中的,字符都是以Unicode编码形式存在的。
而非JVM的内存中,字符是以各种各样的字符集进行编码的,当Java程序与外界交互的时候(如I/O),就会涉及到编码解码问题。
在Java中,不同的字符集都是以Unicode作为桥梁相互转换的。

  • String两个常用API原理介绍。

	public static void main(String[] args) throws Exception {
		String str = "天";
		byte[] b = str.getBytes("gbk");
		show(b);
		
	}
    public static void show(byte[] b){
    	for (int i = 0; i < b.length; i++){
    		System.out.print( b[i] + " ");
    	}
    	System.out.println();
    }

结果:-52 -20

byte[] b = str.getBytes("gbk");

这句话的意思是把str中的字符 天 用gbk字符集进行编码,返回编码后的字节数组。

把-51 -20的2进制的表示形式就是 天 在gbk字符集中的编码。

看着简单,实际上是进行了以下的操作。

相当于从内存中取出天字的Unicode编码,然后利用Unicode到gbk的转换算法,得到天字的gbk编码

如图1




String str2 = new String(b,"gbk");

这句话的意思是把字节数组b,用gbk字符集解码生成新的字符串。

实际上是把字节数组b通过gbk到unicode的转换算法转换成unicode码

如图2



  • 手动转码
public class Test4 {

	public static void main(String[] args) throws Exception {
		String str = "天";
		byte[] b = str.getBytes("gbk");
		show(b);
		String str2 = new String(b,"utf-8");//类似I/O时,用了与发送方不匹配字符集。导致下面需要手动转码
		byte[] b2 = str2.getBytes("utf-8");//企图转回来
		show(b2);
		System.out.println(new String(str2.getBytes("utf-8"),"gbk"));
		
	}
    public static void show(byte[] b){
    	for (int i = 0; i < b.length; i++){
    		System.out.print(b[i]+ " ");
    	}
    	System.out.println();
    }

}


结果如下:
-52 -20 
-17 -65 -67 -17 -65 -67 
锟斤拷


上面的过程与我们开头提到的web中手动转码的问题非常相像。
但是,这次为什么失败了呢。先让我们看看上面代码做了什么。

相当于
1.首先把str通过gbk编码得到了字节数组b
2.把字节数组b用uft8字符集解码生成新的字符str2
3.把str2字符用utf-8字符集编码成字节数组b2

(2)(3)看似可逆。

实际上是

1.首先把str通过gbk编码得到了字节数组b
2.利用字节数组b,通过utf-8到unicode的转换算法生成新的字符str2
3.把字符串str2利用utf-8到unicode的转换算法解码生成字节数组b2

为什么b的值与b2的值不同呢?
现是字节数组通过utf-8解码生成字符,然后字符通过utf-8编码生成字节数组。
明明是可逆的操作,为什么不能还原.让b2与b相等呢?

确实这种转换算法实际上是可逆的。
但问题出现在哪了呢?
其实大家忽略了一个细节我们的b字节数组里面的值是gbk编码形式的。
把一个gbk形式的字节流 通过utf-8到unicode转换算法 转成 unicode码。这个过程之中因为utf-8与gbk的不兼容。导致了转换过程出现了 错误
所以在,你想转回来的时候, 你用错误的结果无法转换回来


再来看看最开头提到的例子:


public class Test4 {

	public static void main(String[] args) throws Exception {
		String str = "天";
		byte[] b = str.getBytes("gbk");
		show(b);
		String str2 = new String(b,"ISO8859-1");
		byte[] b2 = str2.getBytes("ISO8859-1");
		show(b2);
		System.out.println(new String(str2.getBytes("ISO8859-1"),"gbk"));
		
	}
    public static void show(byte[] b){
    	for (int i = 0; i < b.length; i++){
    		System.out.print(b[i]+ " ");
    	}
    	System.out.println();
    }

}



结果如下:

-52 -20 
-52 -20 


为什么用ISO8859-1就成功了呢?为什么用ISO8859-1就可逆了呢?

如果详细说会比较麻烦,你可以认为在这种操作的时候ISO8859-1和gbk的兼容性比较好。

如果你觉得只说兼容性这个词太抽象的话,可以稍微这么理解。

b[]字节数组里面存的是gbk编码,如果用utf-8到unicode的转换算法。这个字节数组里面的某些位用这种算法无法解析,于是数据就出现了错误,数据错误之后,你想转换回来当然不行了(所谓的兼容性不好)。

b[]字节数组里面存的是gbk编码,如果用iso8859-1到unicode的转换算法。虽然字节数组的某些位不是标准的iso8859-1的格式,但是这种算法仍然能按照某种方式解析,所以数据没出现错误,所以你想转换回来的时候,还能转换回来(所谓的兼容性好)。





所以说,手工转码不是万能的。


最佳实践:事先调整好发送方与接收方的字符集,最好别手工转码。




以上纯属个人观点,欢迎讨论。










你可能感兴趣的:(Java)