Java編碼淺析(注意區分三個概念)

Java與Unicode:

 

Java的class文件採用utf8的編碼方式,JVM運行時採用utf16。

 

Java的字符串是unicode編碼的。

 

總之,Java採用了unicode字符集,使之易於國際化。

 

Java支持哪些字符集:

 

即Java能識別哪些字符集並對它進行正確地處理?

 

查看Charset 類,最新的JDK支持160種字符集。可以通過static方法availableCharsets拿到所有Java支持的字符集。

 

Java代碼 
  1. assertEquals(160, Charset.availableCharsets().size());  
  2.   
  3. Set<String> charsetNames = Charset.availableCharsets().keySet();  
  4.   
  5. assertTrue(charsetNames.contains("utf-8"));  
  6. assertTrue(charsetNames.contains("utf-16"));  
  7. assertTrue(charsetNames.contains("gb2312"));  
  8.   
  9. assertTrue(Charset.isSupported("utf-8"));  

 

需要在哪些時候注意編碼問題?

 

1. 從外部資源讀取數據:

 

這跟外部資源採取的編碼方式有關,我們需要使用外部資源採用的字符集來讀取外部數據:

 

Java代碼 
  1. InputStream is = new FileInputStream("res/input2.data");  
  2. InputStreamReader streamReader = new InputStreamReader(is, "GB18030");  

 

這裏可以看到,我們採用了GB18030編碼讀取外部數據,通過查看streamReader的encoding可以印證:

 

Java代碼 
  1. assertEquals("GB18030", streamReader.getEncoding());  

 

正是由於上面我們爲外部資源指定了正確的編碼,當它轉成char數組時才能正確地進行解碼(GB18030 -> unicode):

 

Java代碼 
  1. char[] chars = new char[is.available()];  
  2. streamReader.read(chars, 0, is.available());  
 

但我們經常寫的代碼就像下面這樣:

 

Java代碼 
  1. InputStream is = new FileInputStream("res/input2.data");  
  2. InputStreamReader streamReader = new InputStreamReader(is);  

 

這時候InputStreamReader採用什麼編碼方式讀取外部資源呢?Unicode?不是,這時候採用的編碼方式是JVM的默認字符集,這個默認字符集在虛擬機啓動時決定,通常根據語言環境和底層操作系統的 charset 來確定。可以通過以下方式得到JVM的默認字符集:

 

Java代碼 
  1. Charset.defaultCharset();  
 

爲什麼要這樣?因爲我們從外部資源讀取數據,而外部資源的編碼方式通常跟操作系統所使用的字符集一樣,所以採用這種默認方式是可以理解的。

 

好吧,那麼我通過我的IDE Ideas創建了一個文件,並以JVM默認的編碼方式從這個文件讀取數據,但讀出來的數據竟然是亂碼。爲何?呵呵,其實是因爲通過Ideas創建的文件是以utf-8編碼的。要得到一個JVM默認編碼的文件,通過手工創建一個txt文件試試吧。

 

2. 字符串和字節數組的相互轉換

 

我們通常通過以下代碼把字符串轉換成字節數組:

 

Java代碼 
  1. "string".getBytes();  

 

但你是否注意過這個轉換採用的編碼呢?其實上面這句代碼跟下面這句是等價的:

 

Java代碼 
  1. "string".getBytes(Charset.defaultCharset());  

 

也就是說它根據JVM的默認編碼(而不是你可能以爲的unicode)把字符串轉換成一個字節數組。

 

反之,如何從字節數組創建一個字符串呢?

 

Java代碼 
  1. new String("string".getBytes());  

 

同樣,這個方法使用平臺的默認字符集解碼字節的指定數組(這裏的解碼指從一種字符集到unicode)。

 

 

字符串編碼迷思:

 

Java代碼 
  1. new String(input.getBytes("ISO-8859-1"), "GB18030")  
 

上面這段代碼代表什麼?有人會說: “把input字符串從ISO-8859-1編碼方式轉換成GB18030編碼方式”。如果這種說法正確,那麼又如何解釋我們剛提到的java字符串都採用unicode編碼呢?

 

這種說法不僅是欠妥的,而且是大錯特錯的,讓我們一一來分析,其實事實是這樣的:我們本應該用GB18030的編碼來讀取數據並解碼成字符串,但結果卻採用了ISO-8859-1的編碼,導致生成一個錯誤的字符串。要恢復,就要先把字符串恢復成原始字節數組,然後通過正確的編碼GB18030再次解碼成字符串(即把以GB18030編碼的數據轉成unicode的字符串)。注意,字符串永遠都是unicode編碼的。

 

但編碼轉換並不是負負得正那麼簡單,這裏我們之所以可以正確地轉換回來,是因爲 ISO8859-1 是單字節編碼,所以每個字節被按照原樣 轉換爲String ,也就是說,雖然這是一個錯誤的轉換,但編碼沒有改變,所以我們仍然有機會把編碼轉換回來!

 

總結:

 

所以,我們在處理java的編碼問題時,要分清楚三個概念:Java採用的編碼:unicode,JVM平臺默認字符集和外部資源的編碼。

 

原址:http://www.iteye.com/topic/311583

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章