轉]Java編碼淺析(注意區分三個概念)
PS: 轉自http://www.javaeye.com/topic/311583
Java與Unicode:
Java的class文件採用utf8的編碼方式,JVM運行時採用utf16。
Java的字符串是unicode編碼的。
總之,Java採用了unicode字符集,使之易於國際化。
Java支持哪些字符集:
即Java能識別哪些字符集並對它進行正確地處理?
查看Charset 類,最新的JDK支持160種字符集。可以通過static方法availableCharsets拿到所有Java支持的字符集。
- assertEquals(160, Charset.availableCharsets().size());
- Set<String> charsetNames = Charset.availableCharsets().keySet();
- assertTrue(charsetNames.contains("utf-8"));
- assertTrue(charsetNames.contains("utf-16"));
- assertTrue(charsetNames.contains("gb2312"));
- assertTrue(Charset.isSupported("utf-8"));
需要在哪些時候注意編碼問題?
1. 從外部資源讀取數據:
這跟外部資源採取的編碼方式有關,我們需要使用外部資源採用的字符集來讀取外部數據:
- InputStream is = new FileInputStream("res/input2.data");
- InputStreamReader streamReader = new InputStreamReader(is, "GB18030");
這裏可以看到,我們採用了GB18030編碼讀取外部數據,通過查看streamReader的encoding可以印證:
- assertEquals("GB18030", streamReader.getEncoding());
正是由於上面我們爲外部資源指定了正確的編碼,當它轉成char數組時才能正確地進行解碼(GB18030 -> unicode):
- char[] chars = new char[is.available()];
- streamReader.read(chars, 0, is.available());
但我們經常寫的代碼就像下面這樣:
- InputStream is = new FileInputStream("res/input2.data");
- InputStreamReader streamReader = new InputStreamReader(is);
這時候InputStreamReader採用什麼編碼方式讀取外部資源呢?Unicode?不是,這時候採用的編碼方式是JVM的默認字符集,這個默認字符集在虛擬機啓動時決定,通常根據語言環境和底層操作系統的 charset 來確定。可以通過以下方式得到JVM的默認字符集:
- Charset.defaultCharset();
爲什麼要這樣?因爲我們從外部資源讀取數據,而外部資源的編碼方式通常跟操作系統所使用的字符集一樣,所以採用這種默認方式是可以理解的。
好吧,那麼我通過我的IDE Ideas創建了一個文件,並以JVM默認的編碼方式從這個文件讀取數據,但讀出來的數據竟然是亂碼。爲何?呵呵,其實是因爲通過Ideas創建的文件是以utf-8編碼的。要得到一個JVM默認編碼的文件,通過手工創建一個txt文件試試吧。
2. 字符串和字節數組的相互轉換
我們通常通過以下代碼把字符串轉換成字節數組:
- "string".getBytes();
但你是否注意過這個轉換採用的編碼呢?其實上面這句代碼跟下面這句是等價的:
- "string".getBytes(Charset.defaultCharset());
也就是說它根據JVM的默認編碼(而不是你可能以爲的unicode)把字符串轉換成一個字節數組。
反之,如何從字節數組創建一個字符串呢?
- new String("string".getBytes());
同樣,這個方法使用平臺的默認字符集解碼字節的指定數組(這裏的解碼指從一種字符集到unicode)。
字符串編碼迷思:
- 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平臺默認字符集和外部資源的編碼。