1. 程式人生 > >Java字元編碼知識簡介 .

Java字元編碼知識簡介 .

1、基本資訊
摘要:在Java應用程式特別是Web應用中,經常遇到字元的編碼問題。為了防止出現亂碼,首先需要了解字元編碼的基本概念以及Java是如何處理字元編碼的,這樣就可以有目的地在輸入/輸出環節中增加必要的轉碼。本文將分以下幾部分介紹:
1.   什麼是字符集?什麼是編碼?
2.   常用字符集有哪些?
3.   為什麼會有亂碼?
4.   Java字元編碼
5.   JSP編碼
6.   有沒有萬金油?
7.   參考資料和推薦參閱


2、什麼是字符集?什麼是編碼?
l 字元(Character):是文字與符號的總稱,包括文字、圖形符號、數學符號等。
l 字符集(Charset):就是一組抽象字元的集合。
       字符集常常和一種具體的語言文字對應起來,該文字中的所有字元或者大部分常用字元就構成了該文字的字符集,比如英文字符集。
       一組有共同特徵的字元也可以組成字符集,比如繁體漢字字符集、日文漢字字符集。
       字符集的子集也是字符集。
計算機要處理各種字元,就需要將字元和二進位制內碼對應起來,這種對應關係就是字元
l 編碼(Encoding):
       制定編碼首先要確定字符集,並將字符集內的字元排序,然後和二進位制數字對應起來。根據字符集內字元的多少,會確定用幾個位元組來編碼。
       每種編碼都限定了一個明確的字元集合,叫做被編碼過的字符集(Coded Character Set),這是字符集的另外一個含義。通常所說的字符集大多是這個含義。
 
 
3、常用字符集有哪些?
ASCII:
       American Standard Code for Information Interchange,美國資訊交換標準碼。
       目前計算機中用得最廣泛的字符集及其編碼,由美國國家標準局(ANSI)制定。 它已被國際標準化組織(ISO)定為國際標準,稱為ISO 646標準。 ASCII字符集由控制字元和圖形字元組成。 在計算機的儲存單元中,一個ASCII碼值佔一個位元組(8個二進位制位),其最高位(b7)用作奇偶校驗位。所謂奇偶校驗,是指在程式碼傳送過程中用來檢驗是否出現錯誤的一種方法,一般分奇校驗和偶校驗兩種。奇校驗規定:正確的程式碼一個位元組中1的個數必須是奇數,若非奇數,則在最高位b7添1。偶校驗規定:正確的程式碼一個位元組中1的個數必須是偶數,若非偶數,則在最高位b7添1。
ISO 8859-1:
       全稱ISO/IEC 8859,是國際標準化組織(ISO)及國際電工委員會(IEC)聯合制定的一系列8位字符集的標準,現時定義了15個字符集。
       ASCII收錄了空格及94個“可印刷字元”,足以給英語使用。但是,其他使用拉丁字母的語言(主要是歐洲國家的語言),都有一定數量的變音字母,故可以使用ASCII及控制字元以外的區域來儲存及表示。除了使用拉丁字母的語言外,使用西裡爾字母的東歐語言、希臘語、泰語、現代阿拉伯語、希伯來語等,都可以使用這個形式來儲存及表示。
       ISO 8859-1 (Latin-1) - 西歐語言
       很明顯,iso8859-1編碼表示的字元範圍很窄,無法表示中文字元。 但是,由於是單位元組編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用iso8859-1編碼來表示。 而且在很多協議上,預設使用該編碼。
 
UCS:
       通用字符集(Universal Character Set,UCS)是由ISO制定的ISO 10646(或稱ISO/IEC 10646)標準所定義的字元編碼方式,採用4位元組編碼。
       UCS包含了已知語言的所有字元。除了拉丁語、希臘語、斯拉夫語、希伯來語、阿拉伯語、亞美尼亞語、喬治亞語,還包括中文、日文、韓文這樣的象形文字,UCS還包括大量的圖形、印刷、數學、科學符號。
² UCS-2: 與unicode的2byte編碼基本一樣。
² UCS-4: 4byte編碼, 目前是在UCS-2前加上2個全零的byte。
 
Unicode:
       Unicode(統一碼、萬國碼、單一碼)是一種在計算機上使用的字元編碼。
       Unicode是http://www.unicode.org制定的編碼機制, 要將全世界常用文字都函括進去。它為每種語言中的每個字元設定了統一併且唯一的二進位制編碼,以滿足跨語言、跨平臺進行文字轉換、處理的要求。 1990年開始研發,1994年正式公佈。隨著計算機工作能力的增強,Unicode也在面世以來的十多年裡得到普及。但自從unicode2.0開始,unicode採用了與ISO 10646-1相同的字型檔和字碼,ISO也承諾ISO10646將不會給超出0x10FFFF的UCS-4編碼賦值,使得兩者保持一致。
       Unicode的編碼方式與ISO 10646的通用字符集(Universal Character Set,UCS)概念相對應,目前的用於實用的Unicode版本對應於UCS-2,使用16位的編碼空間。也就是每個字元佔用2個位元組,基本滿足各種語言的使用。實際上目前版本的Unicode尚未填充滿這16位編碼,保留了大量空間作為特殊使用或將來擴充套件。
 
UTF: Unicode 的實現方式不同於編碼方式。
       一個字元的Unicode編碼是確定的,但是在實際傳輸過程中,由於不同系統平臺的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Translation Format,簡稱為 UTF)。
UTF-8:   8bit變長編碼,對於大多數常用字符集(ASCII中0~127字元)它只使用單位元組,而對其它常用字元(特別是朝鮮和漢語會意文字),它使用3位元組。
UTF-16: 16bit編碼,是變長碼,大致相當於20位編碼,值在0到0x10FFFF之間,基本上就是unicode編碼的實現,與CPU字序有關。
 
漢字編碼:
GB2312字集是簡體字集,全稱為GB2312(80)字集,共包括國標簡體漢字6763個。
BIG5字集是臺灣繁體字集,共包括國標繁體漢字13053個。
GBK字集是簡繁字集,包括了GB字集、BIG5字集和一些符號,共包括21003個字元。
GB18030是國家制定的一個強制性大字集標準,全稱為GB18030-2000,它的推出使漢字集有了一個“大一統”的標準。
 
ANSI和Unicode big endia:
       我們在Windows系統中儲存文字檔案時通常可以選擇編碼為ANSI、Unicode、Unicode big endian和UTF-8,這裡的ANSI和Unicode big endia是什麼編碼呢?
 ANSI:使用2個位元組來代表一個字元的各種漢字延伸編碼方式,稱為ANSI編碼。 在簡體中文系統下,ANSI編碼代表GB2312編碼,在日文作業系統下,ANSI編碼代表JIS編碼。
 Unicode big endia: UTF-8以位元組為編碼單元,沒有位元組序的問題。UTF-16以兩個位元組為編碼單元,在解釋一個UTF-16文字前,首先要弄清楚每個編碼單元的位元組序。 Unicode規範中推薦的標記位元組順序的方法是BOM(即Byte Order Mark)。 在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字元,它的編碼是FEFF。而FFFE在UCS中是不存在的字元,所以不應該出現在實際傳輸中。 UCS規範建議我們在傳輸位元組流前,先傳輸字元"ZERO WIDTH NO-BREAK SPACE"。 這樣如果接收者收到FEFF,就表明這個位元組流是Big-Endian的;如果收到FFFE,就表明這個位元組流是Little-Endian的。 因此字元"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。 Windows就是使用BOM來標記文字檔案的編碼方式的。
4、為什麼會亂碼?
       亂碼是個老問題,字元在儲存時的編碼格式如果和要顯示的編碼格式不一樣的話,就會出現亂碼問題。在我們的Web應用中,從底層資料庫編碼、Web應用程式編碼到HTML頁面編碼,如果有一項不一致的話,就會出現亂碼。 所以,解決亂碼問題說難也難說簡單也簡單,關鍵是讓互動系統之間編碼一致。
5、Java字元編碼
       Java在執行期一律以unicode來儲存字元,這樣有利的支援了多語言環境。
       Java在讀取檔案的時候預設是按照系統預設語言(字符集)編碼來解碼檔案,讀取和儲存時候的編碼不一致也導致程式中引數值錯誤,用FileInputStream類讀取檔案可以指定編碼讀取。
       Java在輸出到系統顯示時,會把記憶體中變數字元再通過系統預設語言(字符集)編碼去轉換,所以在輸出過程中也會碰到一系列的編碼問題。
 首先要了解JAVA處理字元的原理。JAVA使用UNICODE來儲存字元資料,處理字元時通常有三個步驟:
  - 按指定的字元編碼形式,從源輸入流中讀取字元資料
  - 以UNICODE編碼形式將字元資料儲存在記憶體中
  - 按指定的字元編碼形式,將字元資料編碼並寫入目的輸出流中。
  所以JAVA處理字元時總是經過了兩次編碼轉換,一次是從指定編碼轉換為UNICODE編碼,一次是從UNICODE編碼轉換為指定編碼。如果在讀入時用 錯誤的形式解碼字元,則記憶體儲存的是錯誤的UNICODE字元。而從最初檔案中讀出的字元資料,到最終在螢幕終端顯示這些字元,期間經過了應用程式的多次 轉換。如果中間某次字元處理,用錯誤的編碼方式解碼了從輸入流讀取的字元資料,或用錯誤的編碼方式將字元寫入輸出流,則下一個字元資料的接收者就會編解碼 出錯,從而導致最終顯示亂碼。
  這一點,是我們分析字元編碼問題以及解決問題的指導思想。
  好,現在我們開始一隻只的解決這些亂碼怪獸。
  一、在JAVA檔案中硬編碼中文字元,在eclipse中執行,控制檯輸出了亂碼。
  例如,我們在JAVA檔案中寫入以下程式碼:
  String text = "大家好";
  System.out.println(text);
  如果我們是在eclipse裡編譯執行,可能看到的結果是類似這樣的亂碼:????。那麼,這是為什麼呢?
  我們先來看看整個字元的轉換過程。
  1. 在eclipse視窗中輸入中文字元,並儲存成UTF-8的JAVA檔案。這裡發生了多次字元編碼轉換。不過因為我們相信eclipse的正確性,所以我們不用分析其中的過程,只需要相信儲存下的JAVA檔案確實是UTF-8格式。
  2. 在eclipse中編譯執行此JAVA檔案。這裡有必要詳細分析一下編譯和執行時的字元編碼轉換。
  - 編譯:我們用javac編譯JAVA檔案時,javac不會智慧到猜出你所要編譯的檔案是什麼編碼型別的,所以它需要指定讀取檔案所用的編碼型別。預設 javac使用平臺預設的字元編碼型別來解析JAVA檔案。平臺預設編碼是作業系統決定的,我們使用的是中文作業系統,語言區域設定通常都是中國大陸,所 以平臺預設編碼型別通常是GBK。這個編碼型別我們可以在JAVA中使用System.getProperty("file.encoding")來查 看。所以javac會預設使用GBK來解析JAVA檔案。如果我們要改變javac所用的編碼型別,就要加上-encoding引數,如javac -encoding utf-8 Test.java。
  這裡要另外提一下的是eclipse使用的是內建的編譯器,並不能新增引數,如果要為javac新增引數則建議使用ANT來編譯。不過這並非出現亂碼的原因,因為eclipse可以為每個JAVA檔案設定字元編碼型別,而內建編譯器會根據此設定來編譯JAVA檔案。
  - 執行:編譯後字元資料會以UNICODE格式存入位元組碼檔案中。然後eclipse會呼叫java命令來執行此位元組碼檔案。因為位元組碼中的字元總是 UNICODE格式,所以java讀取位元組碼檔案並沒有編碼轉換過程。虛擬機器讀取檔案後,字元資料便以UNICODE格式儲存在記憶體中了。
  3. 呼叫System.out.println來輸出字元。這裡又發生了字元編碼轉換。
  System.out.println使用了PrintStream類來輸出字元資料至控制檯。PrintStream會使用平臺預設的編碼方式來輸出字 符。我們的中文系統上預設方式為GBK,所以記憶體中的UNICODE字元被轉碼成了GBK格式,並送到了作業系統的輸出服務中。因為我們作業系統是中文系 統,所以往終端顯示裝置上列印字元時使用的也是GBK編碼。如果到這一步,我們的字元其實不再是GBK編碼的話,終端就會顯示出亂碼。
  那麼,在eclipse執行帶中文字元的JAVA檔案,控制檯顯示了亂碼,是在哪一步轉換錯誤呢?我們一步步來分析。
  - 儲存JAVA檔案成UTF-8後,如果再次開啟你沒有看到亂碼,說明這步是正確的。
  - 用eclipse本身來編譯執行JAVA檔案,應該沒有問題。
  - System.out.println會把記憶體中正確的UNICODE字元編碼成GBK,然後發到eclipse的控制檯去。等等,我們看到在Run Configuration對話方塊的Common標籤裡,控制檯的字元編碼被設定成了UTF-8!問題就在這裡。System.out.println已經把字元編碼成了GBK,而控制檯仍然以UTF-8的格式讀取字元,自然會出現亂碼。

  將控制檯的字元編碼設定為GBK,亂碼問題解決。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

(個人認為:)此例子舉的一點都不好,因為如果common設定成utf-8,輸出的只要是中文肯定是亂碼!

  (這裡補充一點:eclipse的控制檯編碼是繼承了workspace的設定的,通常控制檯編碼裡沒有GBK的選項而且不能輸入。我們可以先在 workspace的編碼設定中輸入GBK,然後在控制檯的設定中就可以看到GBK的選項了,設定好後再把workspace的字元編碼設定改回utf- 8就是。)
6、編碼JSP
       這裡我們主要是介紹JSP頁面的兩個重要屬性:pageEncoding、contentType、charset
       pageEncoding是jsp檔案本身的編碼
       contentType的charset是指伺服器傳送給客戶端時的內容編碼
       charset有兩個作用:一是JSP檔案的編碼方式:在讀取JSP檔案、生成JAVA類時,源JSP檔案中漢字的編碼;二是JSP輸出流的編碼方式:在執行JSP時,往response流裡面寫入資料的編碼方式
       當應用伺服器將JSP編譯成.java檔案時,會根據pageEncoding的設定讀取jsp,結果是由指定的編碼方案翻譯成統一的UTF-8 JAVA原始碼(即.java),如果pageEncoding設定錯了,或沒有設定,出來的就是中文亂碼。
       當應用伺服器利用已經編譯為二進位制的JSP(.class)輸出頁面時,contentType這時則決定了輸出頁面的編碼。
       jsp檔案不像.java,.java在被編譯器讀入的時候預設採用的是作業系統所設定的locale所對應的編碼,比如中國大陸就是GBK,臺灣就是BIG5或者MS950。而一般我們不管是在記事本還是在ue中寫程式碼,如果沒有經過特別轉碼的話,寫出來的都是本地編碼格式的內容。所以編譯器採用的方法剛好可以讓虛擬機器得到正確的資料。
但是jsp檔案不是這樣,它沒有這個預設轉碼過程,但是指定了pageEncoding就可以實現正確轉碼了。
舉個例子:
<%@ page contentType="text/html;charset=utf-8" %>
大都會打印出亂碼,因為我輸入的“你好嗎”是gbk的,但是伺服器是否正確抓到“你好嗎”不得而知。
但是如果更改為
<%@ page contentType="text/html;charset=utf-8" pageEncoding="GBK"%>
這樣就伺服器一定會是正確抓到“你好嗎”了。
7、有沒有萬金油?
       J2EE應用程式是執行在J2EE容器中。在這個系統中,輸入途徑有很多種:一種是通過頁面表單打包成請求(request)發往伺服器的;第二種是通過資料庫讀入;還有第3種輸入比較複雜,JSP在第一次執行時總是被編譯成Servlet,JSP中常常包含中文字元,那麼編譯使用javac時,Java將根據預設的作業系統編碼作為初始編碼。除非特別指定,如在Jbuilder/eclipse中可以指定預設的字符集。
       輸出途徑也有幾種:第一種是JSP頁面的輸出。由於JSP頁面已經被編譯成Servlet,那麼在輸出時,也將根據作業系統的預設編碼來選擇輸出編碼,除非指定輸出編碼方式;還有輸出途徑是資料庫,將字串輸出到資料庫。
由此看來,一個J2EE系統的輸入輸出是非常複雜,而且是動態變化的,而Java是跨平臺執行的,在實際編譯和執行中,都可能涉及到不同的作業系統,如果任由Java自由根據作業系統來決定輸入輸出的編碼字符集,這將不可控制地出現亂碼。
正是由於Java的跨平臺特性,使得字符集問題必須由具體系統來統一解決,所以在一個Java應用系統中,解決中文亂碼的根本辦法是明確指定整個應用系統統一字符集。
指定統一字符集時,到底是指定ISO8859_1 、GBK還是UTF-8呢?
1)如統一指定為ISO8859_1,因為目前大多數軟體都是西方人編制的,他們預設的字符集就是ISO8859_1,包括作業系統Linux和資料庫MySQL等。這樣,如果指定Jive統一編碼為ISO8859_1,那麼就有下面3個環節必須把握:
開發和編譯程式碼時指定字符集為ISO8859_1。
執行作業系統的預設編碼必須是ISO8859_1,如Linux。
在JSP頭部宣告。
2)如果統一指定為GBK中文字符集,上述3個環節同樣需要做到,不同的是隻能執行在預設編碼為GBK的作業系統,如中文Windows。
統一編碼為ISO8859_1和GBK雖然帶來編制程式碼的方便,但是各自只能在相應的作業系統上執行。但是也破壞了Java跨平臺執行的優越性,只在一定範圍內行得通。例如,為了使得GBK編碼在linux上執行,設定Linux編碼為GBK。
那麼有沒有一種除了應用系統以外不需要進行任何附加設定的中文編碼根本解決方案呢?
將Java/J2EE系統的統一編碼定義為UTF-8。UTF-8編碼是一種相容所有語言的編碼方式,惟一比較麻煩的就是要找到應用系統的所有出入口,然後使用UTF-8去“結紮”它。
一個J2EE應用系統需要做下列幾步工作:
開發和編譯程式碼時指定字符集為UTF-8。JBuilder和Eclipse都可以在專案屬性中設定。
使用過濾器,如果所有請求都經過一個Servlet控制分配器,那麼使用Servlet的filter執行語句,將所有來自瀏覽器的請求(request)轉換為UTF-8,因為瀏覽器發過來的請求包根據瀏覽器所在的作業系統編碼,可能是各種形式編碼。關鍵一句:
request.setCharacterEncoding("UTF-8")
在JSP頭部宣告:
<%@ page contentType="text/html;charset= UTF-8" %>
在Jsp的html程式碼中,宣告UTF-8:
<META http-equiv="Content-Type" CONTET="text/html; charset=utf-8">
設定資料庫連線方式是UTF-8。例如連線MYSQL時配置URL如下:
       jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8

一般資料庫都可以通過管理設定設定UTF-8。其他和外界互動時能夠設定編碼時就設定UTF-8,例如讀取檔案,操作XML等。

參考資料
       1 http://java.ccidnet.com/art/3559/20070614/1112507_1.html
       2 http://www.jdon.com/idea/chinesejava.htm
       3 http://www.bitscn.com/java/webservice/200611/82116.html
       4 http://www.javaeye.com/article/97803