string 中文乱码 ,也许大家都经历过,解决方案网上一搜一大堆。有用的无用的,挨个试一下总会有能用的。但是,我们不应该只看中问题的解决方案,更看重的应该是为什么会这么解决,问题产生的原因是什么? 否则,我们永远不会进步。
进入正题,先说一下基本知识,关于字符编码的。众所周知,世界上面有很多的语言,特别多。为了在计算机中存储语言,编码格式就诞生了。
编码格式
通俗的讲呢,编码格式就是 用指定的计算机存储 对应 语言单词 字母之类的。就像ASCII表那样。
乱码的原因
编码格式和解码格式不一样。举个例子,我们约定 一个规则,存数字的时候 ,要先加3,然后再存起来。 读取数字的时候,要先减3 ,才是原本的值。
比如要存5,那么要先加3 ,变成8 ,再存入。 读取时候,取出来的8,要先减3,才能得到5。
我们要按照相同的规则 存取,才能得到正确的值。string 在文件里面底层保存形式是二进制,高级形式就是byte[]。byte[]数组里面的内容可以按照不同的编码格式存放。我们读取字符串的时候,也可以按照不同的解码格式存放。 这样就造成了 乱码。
如何保证不乱码?
从始至终,都用同一种字符格式
方法
认识两个方法 ,下面要用的。
//将调用的string 用给定的编码 转成 字节数组
//charsetName 编码格式
public byte[] getBytes(String charsetName)
//将给定的字节数组,用给定的解码格式 ,生成一个Sting
public String(byte bytes[], String charsetName)
这两个方法里面的charsetName参数,其实并没有编码解码之分,只是想让读者便于理解
例子解读
String path = "D:/one.txt";
File file = new File(path);
if (!file.exists()){
file.createNewFile();
}
FileOutputStream outputStream = new FileOutputStream(file);
//原始内容
String content = "中文englis》》》》";//原始字符串 这时候 与编码格式无关 ,它可以转成任意格式的字节数组
byte[] utfBytes = content.getBytes("UTF-8");//用UTF-8编码 把content编码成 utf格式字节数组
byte[] gbkBytes = content.getBytes("GBK");//用GBK编码 把content编码成 gbk格式字节数组
byte[] isoBytes = content.getBytes("ISO-8859-1");//用ISO-8859-1编码 把content编码成 iso-8859-1格式字节数组
byte[] gb2312Bytes = content.getBytes("GB2312");//用GB2312编码 把content编码成 gb2312格式字节数组
outputStream.write(gb2312Bytes);//将gb2312Bytes 数组写入流,这样 在txt文件中 文字是以 GB2312编码格式存储的
outputStream.flush();
outputStream.close();
上面呢,我们创建了一个txt文件,并且 将"中文englis》》》》"用gb2312编码格式存储了。以后我们从one.txt里面读取这段话的数组,也是gb2312格式的。
读取文件
//以后我们从one.txt文件里面读取流,读到的也是用gb2312格式字节数组
//这里省略从one.txt文件里面读取流 变成字节数组的过程, 读取出来的数组 是和gb2312Bytes 是一样的
//只要字节数组编码格式,和解码格式 是相同的就不会中文乱码
// gb2312Bytes 用gb2312解码
String gb2312ToGb2312 = new String(gb2312Bytes, "GB2312");
System.out.println("gb2312ToGb2312 字节数组编码格式和解码格式相同 "+gb2312ToGb2312);
//如果 字节数组编码格式,和解码格式 不同 就会产生中文乱码
//gb2312编码 ISO-8859-1解码 ——》乱码
String gbk312ToISO = new String(gb2312Bytes, "ISO-8859-1");//gb2312格式字节数组 却用ISO-8859-1的解码
System.out.println("gb2312ToGbk 字节数组编码格式和解码格式不同 "+gbk2312ToISO);//输出乱码
从上面代码可以看出,只要字节数组编码和解码格式相同,就不会产生乱码。
字符串已经乱码(用不一样的解码 构造String)后,如何将乱码字符修复。而不是用原数组 重新构造一个String?
接着上面的代码
//下面 将乱码修复
byte[] temp = gb2312ToISO.getBytes("ISO-8859-1");//因为上面是用ISO-8859-1解码的,所以 用ISO-8859-1编码 逆转到 原先数组 也就是gb2312Bytes数组
System.out.println("乱码字符 ,编码后的数组,和原先gb2312数组比较 "+isEqual(temp,gb2312Bytes));//比较 乱码字符 重新编码后的数组 和 原先的gb2312Bytes数组
System.out.println("乱码字符 ,编码后的数组,和原先iso-8859-1数组比较 "+isEqual(temp,isoBytes));//比较 乱码字符 重新编码后的数组 和 原先的gb2312Bytes数组
System.out.println("temp 数组 解码 "+new String(temp,"GB2312"));//再用GB2312解码
从上面代码可以看见,首先调用getBytes()方法,将gb2312ToISO 再重新用ISO-5589-1编码 转成字节数组。再用gb2312解码重新构造 生成一个的字符串。 可以看见 乱码被修复了。
UTF-8格式的特殊性
为什么要单独强调UTF-8格式呢?
看代码
//注意 如果 你使用UTF-8解码, 意味着 string 只能被 重新编码成 utf-8格式的原数组(解码 编码后 数组相等),不能被重新编码其他格式的数组(解码 编码后 数组不相等)
String gb2312ToUTF= new String(gb2312Bytes, "UTF-8");//字节数组编码 和第二个参数不一致 乱码
System.out.println("gb2312ToUTF 编码gb2312 解码UTF-8 "+gb2312ToUTF);//乱码
//按照上面的方法修复
byte[] gb2312ToUTFBytes = gb2312ToUTF.getBytes("UTF-8");//乱码 重新编码
System.out.println("gbk2312原数组, 和先解码UTF-8,再编码 返回的数组比较"+isEqual(gb2312Bytes,gb2312ToUTFBytes));
System.out.println("utf原数组, 和先解码UTF-8,再编码 返回的数组比较"+isEqual(utfBytes,gb2312ToUTFBytes));
String one = new String(gb2312ToUTFBytes, "UTF-8");
System.out.println("one 此时乱码字符已经不能修复 "+one);
System.out.println("");
//用utf编译的数组,构造GBK格式的字符串
String o = new String(utfBytes, "GBK");
System.out.println(o);
//识图修复
System.out.println(new String(o.getBytes("GBK"),"UTF-8"));
我们首先,构造一个String字符串,原数组是用gb2312编码的,解码是用UTF-8。不出意外,乱码了。当我们按照上面的修复方法 修复时候。发现 这个乱码的String再也不能被修复了。这就是UTF-8的特殊性。你使用UTF-8解码, 意味着 string 只能被 重新编码成 utf-8格式的原数组(解码 编码后 数组相等),不能被重新编码其他格式的数组(解码 编码后 数组不相等)。
总结UTF-8
只要在解码构造String的过程中,字节数组和解码格式 任意一个是UTF-8。那么,生成的乱码字符串将不能被修复。这时候只能 第一次构造String时候, 编码格式和解码格式相等,才能不乱码
BufferedReader和RandomAccessFile的注意事项
System.out.println("\r\nBufferedReader");
//如果在构造 InputStreamReader的时候 没有指定 解码格式 charsetName,那么会默认的使用jvm的字符编码(在IDEA中),cmd窗口会使用操作系统的字符编码。
//现在默认的 一般都是 UTF-8
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(path));
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String read = null;
while ((read=bufferedReader.readLine())!=null){
System.out.println("读取出来的乱码 "+read);
//运用修复方法
read = new String(read.getBytes(), "GB2312");
System.out.println("依旧乱码 不可修复 "+read);
}
bufferedReader.close();
inputStreamReader.close();
System.out.println("\r\n RandomAccessFile");
//RandomAccessFile 是用ISO-8859-1 读取的,所以可以修复
RandomAccessFile randomAccessFile = new RandomAccessFile(path, "rw");
String result = null;
while ((result=randomAccessFile.readLine())!=null){
System.out.println("result "+result);
//修复
System.out.println(new String(result.getBytes("ISO-8859-1"), "gb2312"));
}
可以看见,如果使用BufferedReader 读取字符串,那么在构造InputStreamReader最好指定正常的 编码格式。RandomAccessFile则要注意 他是用ISO-8859-1读取的。