字符编码问题是程序开发中经常遇到的问题,造成的原因其实并不复杂,但需要注意避免和解决。
先来回顾下在转换流中有所涉及过的字符编码。
import java.io.*;
class EncodeStream{
public static void main(String[] args) throws IOException {
// writeText();
readText();
}
public static void readText() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"UTF-8");
char[] buf = new char[10];
int len = isr.read(buf);
String str = new String(buf,0,len);
System.out.println(str);
isr.close();
}
public static void writeText() throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");
osw.write("你好");
osw.close();
}
}
首先可以明确的是在平台下,中文默认是GBK编码,因此,无论是否指定编码要求为“GBK”,“你好”都会编码成4字节长的文件,而用UTF-8编码时,同样的“你好”文件就变成了6字节。如果以GBK编码写入却以UTF-8编码读取,会读取不出内容,反之则会读取出与原数据不相符的内容。这是由GBK编码用两个字节来表示中文而UTF-8编码用三个字节来表示中文造成的。
参考上面的代码来概述一下编码解码原理和代码实现。编码就是字符串变成字节数组,解码就是字节数组变成字符串。String -->byte[ ]使用str.getBytes(charsetName),而byte[ ] -->String使用new String(byte[ ],charsetName)。示例代码如下:
import java.util.*;
class EncodeDemo{
public static void main(String[] args) throws Exception {
String s = "你好";
byte[] b1 = s.getBytes("GBK");
System.out.println(Arrays.toString(b1));
String s1 = new String(b1,"ISO8859-1");
System.out.println("s1="+s1);
//对s1进行ISO8859-1编码
byte[] b2 = s1.getBytes("ISO8859-1");
System.out.println(Arrays.toString(b2));
String s2 = new String(b2,"GBK");
System.out.println("s2="+s2);
}
}
在上面的代码中,演示了如何在解码错误的情况下重新获取正确的数据。但是需要注意的是,如果编码时使用GBK编码表,解码时错误的使用了UTF-8的编码表,则原字节数组是无法这样还原的。究其原因,ISO8859-1编码表中没有中文编码,而UTF-8中是包含中文编码的,因此将GBK编码的字节数组用UTF-8错误的解码之后,再用UTF-8重新编码取不回原先的字节数组。
最后看一个有趣的编码现象,也就可以理解UTF-8的编码判断了。新建一个文本文件,只输入“联通”,保存后关闭。再次打开该文件,会发现原本的内容“联通”变成了乱码。应用下面的代码,观察“联通”在GBK编码后的二进制字节数据就能看出端倪:
class EncodeDemo2{
public static void main(String[] args) throws Exception {
String s = "联通";
byte[] by = s.getBytes("GBK");
for(byte b : by){
System.out.println(Integer.toBinaryString(b&255));
}
}
}
原来,“联通”在被GBK编码后变成了:11000001,10101010,11001101,10101000。查询UTF-8的编码格式表后就能看出,原来其正好符合UTF-8编码表的格式规范。因此,明明是以GBK编码的“联通”在解码时被错误的识别为了UTF-8编码,所以也就无法正常显示原始数据了。