關鍵字 :Java 中文 Unicode 編碼轉換
一、Appetite
在進行詳細的編碼轉換原理闡述之前,我們要作兩件事情:
1。首先檢查操作系統用的語言。以Windows 2003 Server為例,可以在“控制面板”中的“區域和語言設置”中選擇你的國家、語言,還有你的操作系統必須支持的語言。國籍&語言的設定會影響JRE的判斷情況。也許適當的設定能夠幫你解決不少Java語言編碼的問題。
2。更新新版本的JDK。因為新版本的JDK往往能夠更好的支持新的特性,達到良好的語言遲遲效果。例如JDK5.0就已經更形了JDK1.2中的很多語言問題。
二、正餐
2.1 Unicode編碼
2.1.1 Unicode——Java默認的編碼
毫無疑問,Unicode作為容納全球所有語言字符的超級字符集,是Java的首選字符集。Unicode使用兩個字節作為編碼方式,總共容納有6萬多個字符。因為使用16位進行字符編碼,所以也稱為UTF-16。然而即使這樣,UTF-16也并不能充分囊括所有全世界正在使用或者曾經使用的字符,所以必須對其進行擴充。于是后來的Unicode版本已經擴充到了1,112,064個字符,這種規模已經相當大了。但是這樣仍然不能滿足Unicode在世界上的需求,所以必須進行必要的擴充。相比預Unicode1.0,后來的2.0版本已經支持擴展字符了,但是并沒有真正的加入擴展字符集,這種狀況一直持續到了Unicode3.1版,才第一次在Unicode中引入了擴展字符集。但是Unicode的發展腳步并沒有停滯,后來出現了Unicode4.0 標準,而這也正好是現在Java5.0版所必須而且已經提供支持的字符集。
顯然“對增補字符的支持也可能會成為東亞市場的一個普遍商業要求。政府應用程序會需要這些增補字符,以正確表示一些包含罕見中文字符的姓名。出版應用程序可能會需要這些增補字符,以表示所有的古代字符和變體字符。中國政府要求支持 GB18030(一種對整個 Unicode 字符集進行編碼的字符編碼標準),因此,如果是 Unicode 3.1 版或更新版本,則將包括增補字符。臺灣標準 CNS-11643 包含的許多字符在 Unicode 3.1 中列為增補字符。香港政府定義了一種針對粵語的字符集,其中的一些字符是 Unicode 中的增補字符。最后,日本的一些供應商正計劃利用增補字符空間中大量的專用空間收入 50,000 多個日文漢字字符變體,以便從其專有系統遷移至基于 Java 平臺的解決方案。
因此,Java 平臺不僅需要支持增補字符,而且必須使應用程序能夠方便地做到這一點。由于增補字符打破了 Java 編程語言的基礎設計構想,而且可能要求對編程模型進行根本性的修改,因此,Java Community Process 召集了一個專家組,以期找到一個適當的解決方案。該小組被稱為 JSR-204 專家組,使用 Unicode 增補字符支持的 Java 技術規范請求的編號。從技術上來說,該專家組的決定僅適用于 J2SE 平臺,但是由于 Java 2 平臺企業版 (J2EE) 處于 J2SE 平臺的最上層,因此它可以直接受益,我們期望 Java 2 平臺袖珍版 (J2ME) 的配置也采用相同的設計方法。”
UTF-16的編碼方式:
UTF-16 使用一個或兩個未分配的 16 位代碼單元的序列對 Unicode 代碼點進行編碼。值 0x0000 至 0xFFFF 編碼為一個相同值的 16 位單元。增補字符編碼為兩個代碼單元,第一個單元來自于高代理范圍(0xD800 至 0xDBFF),第二個單元來自于低代理范圍(U+DC00 至 U+DFFF)。這在概念上可能看起來類似于多字節編碼,但是其中有一個重要區別:值 0xD800 至 0xDFFF 保留用于 UTF-16;沒有這些值分配字符作為代碼點。這意味著,對于一個字符串中的每個單獨的代碼單元,軟件可以識別是否該代碼單元表示某個單單元字符,或者是否該代碼單元是某個雙單元字符的第一個或第二單元。這相當于某些傳統的多字節字符編碼來說是一個顯著的改進,在傳統的多字節字符編碼中,字節值 0x41 既可能表示字母“A”,也可能是一個雙字節字符的第二個字節。
2.1.2 節省空間的UTF-8
“如果我只能吃一塊巧克力,我絕對不會買上一箱子的巧克力。”
是的,很多時候,特別是我們在處理程序的時候,所使用的并非是所有的Unicode字符,而僅僅是他們其中很小的一個部分,確切的說,這個部分不會比 ASCII多上多少。但是因為使用UTF-16,卻不得不為此付出很多的存儲空間來存儲這些字符,這是一種可恥的浪費。因此,為了便于節省空間,無論是在存儲或者傳輸過程中,如果你只使用到了英文或者拉丁文,那么只需要8位來表示字符就足夠了。這就是UTF-8的設計思想。但是,如果是在漢字或者亞洲語言使用頻率很高的地方,UTF-16依然將是首選。
但是值得注意的是,因為Unicode本身一直在進行版本更新,UTF-8當然也并非一成不變。對于經修改的過得UTF-8編碼,在某些Java API的調用中會出現種種問題,特別是要注意在開發包含增補字符的文本與UTF-8進行轉換的時候,可能會出現嚴重錯誤。
雖然Java本身對官方修訂的UTF-8很熟悉,但是因為Java內部含有一套使用規范編碼的機制,因此實際上,Java在使用UTF-8的時候,就并非使用的是Unicode的UTF-8,而是一種叫做“Java modified UTF-8”(經 Java 修訂的 UTF-8)或(錯誤地)直接稱為“UTF-8”。而在J2SE5.0種,這種編碼被統稱為“modified UTF-8”(經修訂的 UTF-8)。
“經修訂的 UTF-8 和標準 UTF-8 之間之所以不兼容,其原因有兩點。其一,經修訂的 UTF-8 將字符 U+0000 表示為雙字節序列 0xC0 0x80,而標準 UTF-8 使用單字節值 0x0。其二,經修訂的 UTF-8 通過對其 UTF-16 表示法的兩個代理代碼單元單獨進行編碼表示增補字符 。每個代理代碼單元由三個字節來表示,共有六個字節。而標準 UTF-8 使用單個四字節序列表示整個字符。
Java 虛擬機及其附帶的接口(如 Java 本機接口、多種工具接口或 Java 類文件)在
java.io.DataInput
和
DataOutput
接口和類中使用經修訂的 UTF-8 實現或使用這些接口和類 ,并進行序列化。Java 本機接口提供與經修訂的 UTF-8 之間進行轉換的例程。而標準 UTF-8 由
String
類、
java.io.InputStreamReader
和
OutputStreamWriter
類、
java.nio.charset
設施 (facility) 以及許多其上層的 API 提供支持。
由于經修訂的 UTF-8 與標準的 UTF-8 不兼容,因此切勿同時使用這兩種版本的編碼。經修訂的 UTF-8 只能與上述的 Java 接口配合使用。在任何其他情況下,尤其對于可能來自非基于 Java 平臺的軟件的或可能通過其編譯的數據流,必須使用標準的 UTF-8。需要使用標準的 UTF-8 時,則不能使用 Java 本機接口例程與經修訂的 UTF-8 進行轉換。”
UTF-8的編碼方式:
UTF-8 使用一至四個字節的序列對編碼 Unicode 代碼點進行編碼。U+0000 至 U+007F 使用一個字節編碼,U+0080 至 U+07FF 使用兩個字節,U+0800 至 U+FFFF 使用三個字節,而 U+10000 至 U+10FFFF 使用四個字節。UTF-8 設計原理為:字節值 0x00 至 0x7F 始終表示代碼點 U+0000 至 U+007F(Basic Latin 字符子集,它對應 ASCII 字符集)。這些字節值永遠不會表示其他代碼點,這一特性使 UTF-8 可以很方便地在軟件中將特殊的含義賦予某些 ASCII 字符。
2.1.3 同胞兄弟——UTF32
如果要問在Unicode家族誰的肚量最大,毫無疑問的是UTF-32。因為采用32位編碼方式,所以會使得他的容量特別大!因為會有2的32次方個字符!同樣的,會帶來相應的問題,就是UTF-32的空間浪費的也比較嚴重。所以,比般情況下很少使用這種編碼。
UTF-32的編碼方式:
UTF-32 即將每一個 Unicode 代碼點表示為相同值的 32 位整數。很明顯,它是內部處理最方便的表達方式,但是,如果作為一般字符串表達方式,則要消耗更多的內存。
2.1.4 三種編碼方式的比較
Unicode 代碼點 | U+0041 | U+00DF | U+6771 | U+10400 | ||||||||||
表示字形 | ||||||||||||||
UTF-32 代碼單元 |
|
|
|
|
||||||||||
UTF-16 代碼單元 |
|
|
|
|
||||||||||
UTF-8 代碼單元 |
|
|
|
|
關于 Unicode 的編碼,參見“The Unicode Standard, Version 3.0”一書(Addison-Wesley 出版)。
關于 UTF-8 編碼,參見“Java I/O”一書的 399 頁(O'Reilly 出版)。
關于 Java Class File 的格式與 Constant Pool,參見“Java Virtual Machine”一書(O'Reilly出版)。
?
如果想要知道你的系統到底使用什么樣的字符集與字符打交道,可以使用如下代碼片斷得到字符集名稱:
String enc = System.getProperty"file.encoding");
System.out.println(enc ); |
可能會得到下列字符集的名稱:
GB2313:這是簡體中文的標準。
GB18083:這是中文的擴展字符集。
HZ:同樣是一種中文標準。
Big5:這是繁體中文標準。
CNS11643:臺灣的官方標準繁體中文編碼。
Cp937:繁體中文加上 6204 個使用者自定的字符
Cp948:繁體中文版 IBM OS/2 用的編碼方式。
Cp964:繁體中文版 IBM AIX 用的編碼方式。
EUC_TW:臺灣的加強版 Unicode。
ISO2022CN:編碼中文的一套標準。
ISO2022CN_CNS:編碼中文的一套標準,繁體版,襲自 CNS11643。
MS950 或 Cp950:ASCII + Big5,用于臺灣和香港的繁體中文 MS Windows操作系統。
2.2.2 問題來源
在Javac編譯期間,也會先從OS中取得現在使用的字符集,此處設為A,之后把送入的字符串轉化為Unicode編碼,在編譯之后再從Unicode轉化為A型字符集。因此:
- 當你的操作系統國際設定錯誤,編譯時就會產出錯誤的字符集編碼。
- 一些比較lj的編譯器會按照預先設定的字符集,而非OS所使用的字符集進行編碼。
- 原是文件存盤時使用的字符集與編譯器所使用的字符集無法匹配也會產生錯誤。
對于1和2,很好理解。對于3,例如我們使用的OS時GB2312,但是存盤時使用的編碼字符集時UTF-8,這樣java編譯器編譯文件的時候,就把UTF-8字符集當成GB2312字符集來處理,這樣當然會出錯。
可以使用一下代碼片斷來以制定的編碼方式編譯Java文件。
public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException;
-
如果你不能確定你的數據來源或者流向,那么最好不使用Reader和Writer,因為這樣可能造成不必要的信息損失。與其這樣,不如保持其二進制編碼的完整性,留作以后進一步處理。
-
有時候,Reader和Writer之間進行通信的時候也可能出現編碼錯誤,原因在于他們之間直接或者間接的使用到了I/O流,這樣就可能導致編碼轉換時出現不統一的情況。
2.3.2 字符串與字節數組
Java的String類提供了非常豐富的功能借助與此,我們也能達到編碼轉換的功能。
常用的String構造函數如下:
String(byte[] bytes, int offset, int length, String charset);
String(byte[] bytes, String charset);
以上方法可以通過byte數組創建指定字符集的字符串,而下面的方法:
byte[] getBytes(String charset);
則可以將String轉化為指定字符集的byte數組。
此外,還可以通過ByteArrayInputStream 或 ByteArrayOutputStream 串接到 InputStreamReader 或 OutputStreamWriter,來達到轉碼的目的。
2.4 其他問題和解決辦法
然而Java本身涉及到編碼的問題不止這些。曾經有位朋友編寫一個可視花的zip應用程序。非常不幸的是,由于Java 本身的編碼問題,使得他的程序在存儲文件時,如果文件名是含有中文的,那么存儲后的文件名不能夠正確顯示。這個問題困擾了他很久,雖然使用了本文中所提到過的方法,但是依然不能夠解決問題。無奈,在網絡上查找了相關資料,發現如果不用java自己的zip包而改用Apache的zip包問題能夠得到解決。
這就提示我們說,有的時候,當你面臨Java的編碼問題時,不妨利用第三方的工具包嘗試解決往往能夠收到不錯的效果。
三 總結
綜上,本文討論的Java字符編碼問題的來龍去脈,并且給出了相應的解決方法。相信憑借著對問題根源的了解,Java的字符編碼問題一定能夠在實際中得到解決。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
