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讀取的。