本文主要介绍乱码产生及解决,编码方式、常见编码请参考
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html
- 为什么会产生乱码
- 如何避免乱码的产生
- 乱码可以复原吗
1. 为什么会产生乱码
对于这个问题需要先介绍计算机中对字符如何存取的
1.1 计算机如何存储
计算机中是通过二进制的形式对文件、字符等进行存储的。那么问题来了,如何将平时使用的文字转换成二进制呢?这里就涉及到编码知识了。具体参考文章开头链接。
1.2 乱码产生条件
如上图,进行转换时需要涉及到编码问题。如果编码与解码使用的编码不一致,很有可能会导致乱码的产生。
1.3 举个栗子
@Test
public void test3(){
String str = "你好世界";
try {
String GBKEncode = new String(str.getBytes("utf-8"), "GBK");
System.out.println("GBKEncode = " + GBKEncode); // GBKEncode = 浣犲ソ涓栫晫
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
上述通过utf-8进行编码,使用GBK解码。前后编解码不一致,造成乱码
2. 如何避免乱码的产生
前面已经提到乱码产生的条件。只要前后只用相同的编码进行编解码,即可避免乱码的产生。
在java中使用String操作字符串最常见的一个构造方式是
String res = new String(str.getBytes(), “encoding”);
举个栗子:
@Test
public void test4(){
String str = "你好世界";
try {
String encode = new String(str.getBytes(), "GBK");
String decode = new String(encode.getBytes(), "GBK");
System.out.println("decode = " + decode);//decode = 娴g姴銈芥稉鏍櫕
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
前后使用编解码一致,为什么还会出现乱码呢?这是因为encode.getBytes()的缘故。查看jdk源码发现,当没有指定参数时,使用的是系统的编码方式,当前项目中使用的是utf-8,所以出现了乱码情况,下面稍作修改。
@Test
public void test4(){
String str = "你好世界";
try {
String encode = new String(str.getBytes(), "GBK");
String decode = new String(encode.getBytes("GBK"), "utf-8");
System.out.println("decode = " + decode);//decode = 你好世界
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
3. 乱码可以复原吗
既然提出了这个问题,说明复原还是可能的,只不过要求比较苛刻。
3.1. 编解码过程中不能出现大小类型编码方式混合的情况。
什么意思:
@Test
public void run1(){
String str = "你好hello";
try {
String encode1 = new String(str.getBytes("utf-8"), "GBK");
// 黑洞出现
String decode1 = new String(encode1.getBytes("ISO-8859-1"), "utf-8");
String encode2 = new String(decode1.getBytes("utf-8"), "ISO-8859-1");
String decode2 = new String(encode2.getBytes("GBK"), "utf-8");
System.out.println("decode = " + decode2); //decode = ???hello
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
如代码所示,其中大小类型编码方式混合使用,会造成丢失(黑洞现象),即小类型编码无法表示大类型编码所代表的字符,会按照该类型编码的最大值进行表示。这样就造成了对应字符不正确的情况
3.2 . 不能使用固定编码进行编码,用动态编码进行解码
固定编码:是指固定用几个字节来表示一个字符。这种情况编码中不用存储字符所需字节大小信息。如:GBK等
动态编码:指的是根据不同字符使用不同位数的字节进行表示。在字节存储中包含了多少位字节表示一个字符。如UTF-8,其编码方式如(1110xxxx 10xxxxxx 10xxxxxx),代表三个字节表示一个字符。(110xxxxx 10xxxxxx),代表两个字节表示一个字符,(xxxxxxxx)代表一个字节表示一个字符。
@Test
public void run1(){
String str = "你好hello";
try {
String encode1 = new String(str.getBytes("GBK"), "UTF-8");
String decode1 = new String(encode1.getBytes("UTF-8"), "GBK");
System.out.println("固定 --> 变长 = " + decode1);
String encode2 = new String(str.getBytes("UTF-8"), "GBK");
String decode2 = new String(encode2.getBytes("GBK"), "UTF-8");
System.out.println("变长 --> 固定 = " + decode2);
//固定 --> 变长 = 锟斤拷锟絟ello
//变长 --> 固定 = 你好hello
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
所以:如果使用固定编码进行编码。变长编码进行解码,会导致变长编码无法正常进行编码读取。反过来,则是可以的。
这是两个基本条件,但也存在特例,如:
(摘自大神的文章:传送门)
这个违反了第一条,这是因为使用大类型编码后,产生的字节被小类型编码一个一个的表示再用小类型编码进行编码。其实整个过程并没有做过多的编码处理。所以结果是正确的。
总结:其实乱码的产生,就是由于编码和解码时所使用的编码方式没能对应上。统一编码,从源头解决问题!