Java:IO流之:中文亂碼的探究
前言-----
整理是有價值的,記憶無法相信,~
目錄
一.理解各種編碼規則
我的轉載部落格----"字串的前世今生", 大家好好看看,先對各種編碼規則有所瞭解,這是重要的基礎!!!
二.輸出"簡"字在4種編碼規則下對應的十六進位制值
看我上面的文章的話,大家應該知道常用的幾種編碼規則有:
ANSI:這個不是ASCII的意思而是採用本地編碼的意思如果你是中文的作業系統,就會使GBK,如果是英文的就會是ISO-8859-1
Unicode UNICODE原生的編碼方式
Unicode big endian 另一個 UNICODE編碼方式(大端儲存)
UTF-8 最常見的UTF-8編碼方式,數字和字母用一個位元組, 漢字用3個位元組。
demo截圖:
結果:
三. 為何會亂碼, 如何不亂碼
- 為何為亂碼?
根據上面可知: "簡"在GBK下對應的編碼是bc f2, 在UTF-8下對應的是e7 ae 80
- 如何不亂碼?
只要把握一個原則: 什麼編碼規則下的數字,就應該用對應編碼去解碼出對應的字元 .只要把握出這個規則就不會亂碼了
三.從檔案讀入中文字元時, 如何不亂碼
- 1.首先你要檢視檔案是用什麼編碼規則儲存的(以記事本為例)
不知道的話,可以自己選擇一種型別儲存
- 2.用位元組流讀取
用位元組流讀取中文的原理:
用位元組流讀取出位元組陣列,再利用String(byte[] ,String CharSetName),將位元組陣列按照對應的編碼規則轉化為String
demo1:
測試檔案內容: "簡1998"(編碼規則:GBK)
public void testGBK(File f) { checkFile(f); //位元組流進行讀取 try(FileInputStream fis = new FileInputStream(f)){ byte []myByte =new byte[(int)f.length()]; fis.read(myByte); String str = new String(myByte, "GBK"); System.out.println(str); }catch(Exception e) { e.printStackTrace(); } }
結果:
demo2:
測試檔案內容: "簡1998"(編碼規則:UTF-8)
public void testUTF8(File f) {
checkFile(f);
//用位元組流讀取
try(FileInputStream fis = new FileInputStream(f)){
byte []myByte = new byte[(int)f.length()];
fis.read(myByte);
String str = new String(myByte,"UTF8");//用UTF8去識別對應的位元組陣列,轉化為對應的字元
System.out.println(str);
}catch(Exception e) {
e.printStackTrace();
}
}
結果:
demo3:
測試檔案內容: "簡1998"(編碼規則:Unicode)
public void testUnicode(File f) {
checkFile(f);
//用位元組流進行讀取
try(FileInputStream fis = new FileInputStream(f)){
byte []myByte =new byte[(int)f.length()];
fis.read(myByte);
String str = new String(myByte,"Unicode");
System.out.println(str);
}catch(Exception e) {
e.printStackTrace();
}
}
結果:
- 3.用字元流讀取
用字元流讀取時, 其實內部有用到緩衝區(記憶體中),然後直接傳位元組陣列給程式,程式再用對應的編碼規則去解碼它,但是此時這個編碼規則取決於誰呢?
其實取決於FileReader, 而FileReader使用的編碼方式是Charset.defaultCharset()的返回值,如果是中文的作業系統,就是GBK.
FileReader是不能手動設定編碼方式的,為了使用其他的編碼方式,只能使用InputStreamReader來代替,像這樣:
new InputStreamReader(new InputStream(File f) ,Charset.forName("Unicode"), 這樣我們就可以指定對應的解碼規則了
demo 1:
測試檔案內容: "簡1998"(編碼規則:GBK)
public void testGBK(File f) {
checkFile(f);
try(InputStreamReader isr = new InputStreamReader(new FileInputStream(f), Charset.forName("GBK"))){
char [] myChar = new char[(int)f.length()];
isr.read(myChar);
System.out.println(myChar);
}catch(Exception e) {
e.printStackTrace();
}
}
結果:
demo2
測試檔案內容: "簡1998"(編碼規則:UTF-8)
public void testUTF8(File f) {
checkFile(f);
try( InputStreamReader isr = new InputStreamReader( new FileInputStream(f), Charset.forName("UTF-8") ) ){
//雖然返回的是位元組長度,但是如果一個字元是多個位元組的話,字元數一定是小於位元組數的,所以長度肯定是可以夠讀的
char [] myChar = new char[(int)f.length()];
isr.read(myChar);
System.out.println(myChar);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
demo3:
測試檔案內容: "簡1998"(編碼規則:Unicode)
public void testUnicode(File f) {
checkFile(f);
try( InputStreamReader isr =new InputStreamReader(new FileInputStream(f),Charset.forName("Unicode")) ){
char [] myChar =new char[(int)f.length()];
isr.read(myChar);
System.out.println(myChar);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
四.寫入中文字元到文字時, 如何不亂碼
首先我們要知道被寫入的文字是用什麼編碼機制,就像上面所說的那樣....然後我們用String的getBytes(byte[], String CharsetName ), 將要輸入的字串轉化為對應編碼下的位元組陣列,然後再寫入.
1.用位元組流寫入(FIleOutputStream)
demo1:
測試檔案:內容為空(用GBK儲存)
public void testOutputGBK(File f) {
checkFile(f);
//用位元組流寫到檔案
String str = "簡1998";
try( FileOutputStream fos = new FileOutputStream(f) ){
byte [] myByte = str.getBytes("GBK");
fos.write(myByte);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
demo2:
測試檔案:內容為空(用UTF-8儲存)
public void testOutputUTF8(File f) {
checkFile(f);
String str = "簡1998";
try(FileOutputStream fos = new FileOutputStream(f)){
byte [] myByte = str.getBytes("UTF-8");
fos.write(myByte);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
demo3
測試檔案:內容為空(用Unicode儲存)
public void testOutputUnicode(File f) {
checkFile(f);
String str = "簡1998";
try(FileOutputStream fos = new FileOutputStream(f)){
byte[] myByte = str.getBytes("Unicode");
fos.write(myByte);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
2.用字元流寫入(FIleOutputStream)
跟用字元流讀一樣, 如果用FIleWriter去寫的話,那麼FileWriter只會用Charset.defaultCharset()的返回值(中文作業系統下預設是GBK)去解碼字元, 然後寫入緩衝區,接著再從緩衝區把位元組寫入檔案中, 我們必須要用OutputStreamWriter去寫入, 像這樣
new OutputStreamWriter( new FIleOutputStream(f), Charset.forName("UTF-8")) 這樣, 原因在於我們能改變解碼方式
demo1:
測試檔案:內容為空(用GBK儲存)
public void testOutputGBK(File f) {
checkFile(f);
//用字元流寫到檔案
String str = "簡1998";
try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f), Charset.forName("GBK"))){
osw.write(str);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
demo2:
測試檔案:內容為空(用UTF-8儲存)
public void testOutputUTF8(File f) {
checkFile(f);
String str = "簡1998";
try( OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),Charset.forName("UTF-8"))){
osw.write(str);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
demo3:
測試檔案:內容為空(用Unicode儲存)
public void testOutputUnicode(File f) {
checkFile(f);
String str = "簡1998";
try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),Charset.forName("Unicode"))){
osw.write(str);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果:
五.如何去掉UTF-8中的BOM標記
大家應該會發現,從UTF-8格式的檔案讀入中文時,會出現 ? 為什麼"簡"字前面有一個?
如果是使用記事本另存為UTF-8的格式,那麼在第一個位元組有一個標示符,叫做BOM用來標誌這個檔案是用UTF-8來編碼的。
BOM對應的十六進位制是: ee bb bf, 那麼我們如何把讀入的BOM給去除呢?
demo:
測試檔案:簡1998(UTF-8儲存)
public void removeBOM(File f){
checkFile(f);
//用位元組流讀取
try(FileInputStream fis = new FileInputStream(f)){
byte []myByte = new byte[(int)f.length()];
fis.read(myByte);
for(byte i : myByte) {
int k = i&0xff;
System.out.println(Integer.toHexString(k));
}
System.out.println("我們發現了BOM標緻對應的16進位制是ee ,ef,bf");
byte [] removeBOM = new byte[myByte.length];
for(int i= 0 ;i < myByte.length ;i++) {
if(i==0 || i==1 || i==2)
continue;
removeBOM[i -3] = myByte[i];
}
System.out.println("我們將取出BOM的removeBOM進行轉換,就不會出現中文字元了");
String str = new String(removeBOM,"UTF8");//用UTF8去識別對應的位元組陣列,轉化為對應的字元
System.out.println(str);
}catch(Exception e) {
e.printStackTrace();
}
}
測試結果: