1. 程式人生 > >Java:IO流之:中文亂碼的探究

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();
                 }
}
		

測試結果: