通过“中文”两个字来深度剖析各种不同的编码方式,彻底理解乱码是如何产生的?

来源:互联网 发布:少年泰坦 知乎 编辑:程序博客网 时间:2024/04/27 01:15
  最近在和吉林的学生做模拟面试的时候,我一直在问他们一个问题,就是让他们阐述一下乱码的产生过程,回答的不是很透彻,都只是简单的说在配置文件里加个过滤器就行了,感觉只是会机械的使用,不太明白具体的过程,今天写了一篇文章,希望对编码方式有一个更深刻的理解:
 
编码:把字符转换成计算机能够识别的01序列。
解码:把01序列转换为人类认知的字符。
不同的编码方式最本质的不同就是各种编码方式都有自己独特的字符和字节的对照表

Java用unicode编码,也就是用16位来编写一个字符。
utf8:用三个字节来编码一个中文字符。
.Java源文件(含有中文的话)是用:GBK编码。
.class文件用UTF8编码。例如汉字“中文”被编译成.class文件后,“中”字用
e4:b8:ad来存储;“文”字用e6:96:87来存储。//.class文件可用ue32查看
Java虚拟机内存中是用unicode来编码,unicode用两个字节来表示一个中文字符,在内存当中“中字”用4e:2d来表示;“文”字用65,87来表
示。
字符串在内从中的01序列可以通过下面的方式来输出:
例如:
(一):中文的Unicode编码方式:4e:2d:65,87
 public void testEncoding(){
  try {
   String str = "中文";
   byte[] bytes = str.getBytes("UTF-16");
   //4e:2d:65:87
   log(EncodingUtils.toHexString(bytes));//EncodingUtils为手写的工具类,用来将序列用十六进制(也就是
unicode的编码方式)输出
   
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 

技巧:查看中午字符的unicode编码的快捷方式,在Word里输入中文,如“中”字,然后把鼠标放在“中”字的后面,然后按“alt+x”,即可
观察到“中”字的unicode编码方式。

(二):“中文”字符的GBK编码:
 //"中文"的GBK编码是:d6:d0:ce:c4
 public void testEncoding2(){
  
  try {
   //正确的GBK编码的字节流
   byte[] bytes = new byte[]{(byte)0xd6,(byte)0xd0,(byte)0xce,(byte)0xc4};
   
   //指定了正确的编码之后,能够转换为正确的unicode编码的字符
   String unicodeChar = new String(bytes,"GBK");
   
   log(unicodeChar);//输出结果是“中文”
   
   log("在内存中的编码是:"+EncodingUtils.toHexString(unicodeChar));//输出结果是4e:2d:65:87(为中文的
unicode编码)
   
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

步骤:1:创建一个字符流,该字符流的特点是如果按照GBK对该字符流进行编码,则按照GBK的字符对照表,该字符流对应的字符串是中文,所
以log(unicodeChar)打印出来的是中文。
2:当用Unicode来显示中文的时候,根据unicode的字符对照表,“中文”用unicode的编码的话,则字节流序列为4e:2d:65:87,所以要打印出
“中文”在内存中的字节流表示的话,则为4e:2d:65:87,可见字符是最主要的,字符是不变的,变的是不同编码方式的字节流。

(二):“中文”字符的UTF8编码:
 //"中文"的UTF-8编码是:e4:b8:ad:e6:96:87
 public void testEncoding3(){
  
  try {
   //正确的UTF-8编码的字节流
   byte[] bytes = new byte[]{(byte)0xe4,(byte)0xb8,(byte)0xad,(byte)0xe6,(byte)0x96,(byte)0x87};
   
   //指定了正确的编码之后,能够转换为正确的unicode编码的字符
   String unicodeChar = new String(bytes,"UTF-8");
   
   log(unicodeChar);//输出结果是“中文”
   
   log("在内存中的编码是:"+EncodingUtils.toHexString(unicodeChar));//输出结果是4e:2d:65:87(为中文的
unicode编码)
   
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
这个的输出结果和分析思路和(二)是一样的,不做赘述。

(四)
//"中文"的UTF-8编码是:e4:b8:ad:e6:96:87
 public void testEncoding4(){
  
  try {
   //正确的UTF-8编码的字节流
   byte[] bytes = new byte[]{(byte)0xe4,(byte)0xb8,(byte)0xad,(byte)0xe6,(byte)0x96,(byte)0x87};
   
   //指定了错误的编码,得到错误的字符
   String unicodeChar = new String(bytes,"GBK");//因为GBK编码是两个字节表示一个字符,而现在的bytes是六个
字节,所以得到的就是三个字符,例如假设这时按照GBK的编码对照表,字节流bytes转化为字符的是“涓枃”,所以这时的字符unicodeChar
是涓枃)
   log("unicodeChar 是:"EncodingUtils.toHexString(unicodeChar));//输出结果是6d93:e15f:6783:这是字符涓
枃的unicode表示,注意此时的输出结果不是“涓枃”,该语句是打印字节流,而不是字符串
   
   //按原路返回
   byte[] nextbytes = unicodeChar.getBytes("GBK");
   log(EncodingUtils.toHexString(nextbytes));//输出结果是e4:b8:ad:e6:96:87:,按照原路返回
   
   String nextChar = new String(nextbytes,"UTF-8");
   
   log(unicodeChar);//输出结果是“涓枃”
   log("在内存中的编码是:"+EncodingUtils.toHexString(unicodeChar));//输出结果是“在内存中的编码是:
6d93:e15f:6783:”
   
   log("nextChar ->");//输出结果是“中文”
   log(nextChar);
   log("在内存中的编码是:"+EncodingUtils.toHexString(nextChar));//输出结果是“在内存中的编码是:
4e2d:6587:”
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

(五):
public void testEncoding5(){
  
  try {
   //String str = "联通";
   //正确的UTF-8编码的字节流
   byte[] bytes = new byte[]{(byte)0xe8,(byte)0x81,(byte)0x94,(byte)0xe9,(byte)0x80,(byte)
0x9a};//str.getBytes("UTF-8");
   
   log(EncodingUtils.toHexString(bytes));//输出结果是“e8:81:94:e9:80:9a:”
   
   //指定了错误的编码,得到错误的字符
   String unicodeChar = new String(bytes,"GBK");//此时unicode的值是“鑱旈??”?说明了在GBK编码中字节
0x80,0x9a根本没有对应的字符,按照GBK的编码方式,根本不知道这两个字节该转换为何种字符,就显示为字符“?”
   log(unicodeChar);//显示结果是“鑱旈??”
   log("在内存中的编码是:"+EncodingUtils.toHexString(unicodeChar));显示结果是“9471:65c8:fffd:fffd:”
   log(unicodeChar.length()+"");//显示结果是"4"
   //按原路返回,不一定能够返回!!!
   byte[] nextbytes = unicodeChar.getBytes("GBK");
   log(EncodingUtils.toHexString(nextbytes));//显示结果是“e8:81:94:e9:3f:3f:”其中3f是GBK中“?”的编码
字节
   
   String nextChar = new String(nextbytes,"UTF-8");
   
   
   
   log("nextChar ->");
   log(nextChar);//显示结果是“联???”,因为按照e8:81:94还可以显示为“联”,而“e9:3f:3f”在UTF8中已经无
法根据编码表来显示字符了。
   log("在内存中的编码是:"+EncodingUtils.toHexString(nextChar));//输出结果是“054:fffd:003f:003f:”,即
是“联???”的unicode编码方式
   
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

总结:最本质的就是编码对照表,即   字符《---------编码规则-------------》字节流,抓住这个主线,一步步分析就行了。
 
 
 
EncodingUtils.java的源代码:
package org.topxp.utils;
/**
 * 编码测试工具类
 * @author <a href='mailto:tengfei.lee@gmail.com'>Kenny Lee</a>
 * <br/>2006-8-1 15:28:05
 */
public class EncodingUtils {
 final static char[] digits = {
  '0' , '1' , '2' , '3' , '4' , '5' ,
  '6' , '7' , '8' , '9' , 'a' , 'b' ,
  'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
  'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
  'o' , 'p' , 'q' , 'r' , 's' , 't' ,
  'u' , 'v' , 'w' , 'x' , 'y' , 'z'
     };
 
 /**
  * 返回一个int值的二进制表示的字符串
  * @param i
  * @return
  */
 public static String toBinaryString(int i){
  return toUnsignedString(i,32,1);
 }
 
 /**
  * 返回一个char值的二进制表示的字符串
  * @param c
  * @return
  */
 public static String toBinaryString(char c){
  return toUnsignedString(c,16,1);
 }
 
 /**
  * 返回一个字符串的二进制表示的字符串
  * @param s
  * @return
  */
 public static String toBinaryString(String s){
  char[] cs = s.toCharArray();
  StringBuffer sb = new StringBuffer();
  for(int i=0; i<cs.length; i++){
   sb.append(toBinaryString(cs[i]));
   sb.append(":");
  }
  return sb.toString();
 }
 
 /**
  * 返回一个byte[]的二进制表示的字符串
  * @param bs
  * @return
  */
 public static String toBinaryString(byte[] bs){
  StringBuffer sb = new StringBuffer();
  for(int i=0; i<bs.length; i++){
   sb.append(toBinaryString(bs[i]));
   sb.append(":");
  }
  return sb.toString();
 }
 
 /**
  * 返回一个byte的二进制表示的字符串
  * @param c
  * @return
  */
 public static String toBinaryString(byte c){
  return toUnsignedString(c,8,1);
 }
 
 /**
  * 返回一个int的十六进制表示的字符串
  * @param i
  * @return
  */
 public static String toHexString(int i){
  return toUnsignedString(i,8,4);
 }
 
 /**
  * 返回一个char的十六进制表示的字符串
  * @param c
  * @return
  */
 public static String toHexString(char c){
  return toUnsignedString(c,4,4);
 }
 
 /**
  * 返回一个byte的十六进制表示的字符串
  * @param b
  * @return
  */
 public static String toHexString(byte b){
  return toUnsignedString(b,2,4);
 }
 
 /**
  * 返回一个String的十六进制表示的字符串
  * @param s
  * @return
  */
 public static String toHexString(String s){
  char[] cs = s.toCharArray();
  StringBuffer sb = new StringBuffer();
  for(int i=0; i<cs.length; i++){
   sb.append(toHexString(cs[i]));
   sb.append(":");
  }
  return sb.toString();
 }
 
 /**
  * 返回一个byte[]的十六进制表示的字符串
  * @param bs
  * @return
  */
 public static String toHexString(byte[] bs){
  StringBuffer sb = new StringBuffer();
  for(int i=0; i<bs.length; i++){
   sb.append(toHexString(bs[i]));
   sb.append(":");
  }
  return sb.toString();
 }
 private static String toUnsignedString(int i,int length,int shift){
  char[] buf = new char[32];
  for(int j=0; j<32; j++){
   buf[j] = '0';
  }
  int charPos = 32;
  int radix = 1 << shift;
  int mask = radix - 1;
  do {
      buf[--charPos] = digits[i & mask];
      i >>>= shift;
  } while (i != 0);
  
  return new String(buf,32-length,length);
 }
}

原创粉丝点击