众所周知,由于有很多编码方式,不同编码方式之间也不兼容所以极其容易乱码。最近在处理日文的数据,被编码问题搞得有点恼火,所以认真总结了一下java中的编码问题。下面是干货。



问题一:Java中使用的是Unicode,这个和utf-8这些有什么不同地方?



答:



     Unicode只是一个字符映射方式,计算机中只能保存二进制码,所以需要建立一个字符和二进制码之间的映射关系。Unicode就是这样的一个关系。Unicode使用了2个字节来对字符进行编码,它规定了每个字符的一个具体编码。但是Unicode中仅仅规定了编码值,并没有规定这些二进制值应该怎么样去保存。如果全部字符按照Unicode来保存的话,会导致单字符也就是ASCII字符保存太过浪费。所以针对这个问题又有了一众编码方式。常见的UTF-8就是其中的一种。utf-8编码方式中其字符的二进制数采用Unicode的值。根据不同字符的Unicode值进行不同的编码,其映射表如下:






其中高位的1个数表示这个utf-8编码的字节个数。一般中文编码都是使用了三个字节进行编码。



     那么,Java中采用Unicode处理字符串这是什么意思呢?就是说Java在内存中保存字符的二进制数,是使用了Unicode值进行保存的。请注意这里和具体什么编码没有任何关系。从UTF-8这些编码方式要解决的问题可以看出来,一般只有在网络传输或者数据需要落地保存时候才会涉及,在内存中其实都是一样,都是Unicode值。这也解释了为什么Java中char是2个字节,而且Java中的char是可以保存汉子字符的。因为一个Unicode就要使用2个字节的空间。





问题二:什么情况下乱码的字符串可以通过指定正确的编码恢复回来



答:



     这个就涉及到java中如何处理字符串编码了。




public static void main(String[] args) throws UnsupportedEncodingException { 
  
 
  

               String chinese = "I am 中国人" ;// java内部编码 
  
 
  

                // 这里能把乱码恢复过来是因为都是使用了单字节编码方式进行了解码,也就是说这样是不会损失原来的二进制信息 
  
 
  

               String gbkChinese = new String(chinese.getBytes( "GBK"), "ISO-8859-1"); // 转换成gbk 编码 
  
 
  

               System. out.println( gbkChinese); 
  
 
  

               String unicodeChinese = new String(gbkChinese .getBytes("ISO-8859-1" ), "GBK" );// java内部编码 
  
 
  

               System. out.println( unicodeChinese); // 中文 
  
 
  

               String utf8Chinese = new String(unicodeChinese .getBytes("UTF-8" ), "ISO-8859-1" );// utf--8编码 
  
 
  

               System. out.println( utf8Chinese); // 乱码 
  
 
  

                unicodeChinese = new String(utf8Chinese .getBytes("ISO-8859-1" ), "UTF-8" );// java内部编码 
  
 
  

               System. out.println( unicodeChinese); // 中文 
  
 
  

                // 使用多字节编码方式进行解码就不行了 
  
 
  

                gbkChinese = new String( chinese.getBytes( "utf-8"), "gbk"); 
  
 
  

               System. out.println( new String(gbkChinese .getBytes("gbk" ), "utf-8" )); 
  
 
  

                // 这样的方式有乱码是因为, gbk编码中文字符是双字节编码, utf-8是三字节编码,这样互相转换会造成信息丢失,所以恢复不过来。 
  
 
  
 
   
 
  

         }



getBytes(字符编码)这个函数语义就是将字符串使用指定的编码方式进行编码。具体意思就是一个字符串在JVM中按照其对应的Unicode值保存在内存中,调用这个方法以后,按照制定的编码方式将其进行编码。比如说制定的是utf-8,那么就按照utf-8的方式进行编码。显然,编码以后其byte的数量会因为字符串中字符的类型而发生改变。


     那么如果某个字符串在进行编码以后,按照错误的方式进行解码以后,是否可以将其恢复出来呢?


     答案是不一定,为什么?因为解码方式不同会导致会有丢弃原来二进制值得现象,这样的话导致原来的值发生了变化,所以不能再恢复出来。只有当解码方式是单字节并且没有对原来二进制值进行修改的情况下才能恢复出来。比如说IS0-8859-1这个编码在碰到没有对应字符编码的时候会填充3f这个16进制值进去。这样的就会对原来字符串的值进行了修改,自然也就没有办法再恢复出来了。


     但是使用ISO-8859-1这样的编码进行解码时候导致乱码则可以恢复出来,这是因为它是单字节编码,每个字节都会被解码,虽然解码错误但是依然可逆。



总结:


     总结来说,乱码恢复不太容易,这个涉及到当时编码对字符串的处理方式。所以在Java开发中发现乱码时候不要指望String unicodeChinese = new String(gbkChinese .getBytes("ISO-8859-1" ), "GBK" );这样的方式进行恢复,因为有极大的可能是恢复不成功的。如果发现乱码,一定要找到是哪里编/解码不一致,将其改一致这样才能解决问题的根源。