1. 概述
在 下面的描述中,將以"中文"兩個字為例,經查表可以知道其GB2312編碼是"d6d0 cec4",Unicode編碼為"4e2d 6587", UTF編碼就是"e4b8ad e69687"。注意,這兩個字沒有iso8859-1編碼,但可以用iso8859-1編碼來"表示"。
2. 編碼基本知識
最早的編碼是iso8859-1,和ascii編碼相似。但為了方便表示各種各樣的語言,逐漸出現了很多標准編碼,重要的有如下幾個。
2.1iso8859-1
屬於單字節編碼,最多能表示的字符范圍是0-255,應用於英文系列。比如,字母'a'的編碼為0x61=97。
很明顯,iso8859-1編碼表示的字符范圍很窄,無法表示中文字符。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用 iso8859-1編碼來表示。而且在很多協議上,默認使用該編碼。比如,雖然"中文"兩個字不存在iso8859-1編碼,以gb2312編碼為例,應該是"d6d0 cec4"兩個字符,使用iso8859-1編碼的時候則將它拆開為4個字節來表示:"d6 d0 ce c4"(事實上,在進行存儲的時候,也是以字節為單位處理的)。而如果是UTF編碼,則是6個字節"e4 b8 ad e6 96 87"。很明顯,這種表示方法還需要以另一種編碼為基礎。
2.2GB2312/GBK
這就是漢子的國標碼,專門用來表示漢字,是雙字節編碼,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。其中gbk編碼能夠用來同時表示繁體字和簡體字,而gb2312只能表示簡體字,gbk是兼容gb2312編碼的。
2.3unicode
這是最統一的編碼,可以用來表示所有語言的字符,而且是定長雙字節(也有四字節的)編碼,包括英文字母在內。所以可以說它是不兼容iso8859-1編碼的,也不兼容任何編碼。不過,相對於iso8859-1編碼來說,uniocode編碼只是在前面增加了一個0字節,比如字母'a'為"00 61"。
需要說明的是,定長編碼便於計算機處理(注意GB2312/GBK不是定長編碼),而unicode又可以用來表示所有字符,所以在很多軟件內部是使用unicode編碼來處理的,比如java。
2.4UTF
考慮到unicode編碼不兼容iso8859-1編碼,而且容易占用更多的空間:因為對於英文字母,unicode也需要兩個字節來表示。所以 unicode不便於傳輸和存儲。因此而產生了utf編碼,utf編碼兼容iso8859-1編碼,同時也可以用來表示所有語言的字符,不過,utf編碼是不定長編碼,每一個字符的長度從1-6個字節不等。另外,utf編碼自帶簡單的校驗功能。一般來講,英文字母都是用一個字節表示,而漢字使用三個字節。
注意,雖然說utf是為了使用更少的空間而使用的,但那只是相對於unicode編碼來說,如果已經知道是漢字,則使用 GB2312/GBK無疑是最節省的。不過另一方面,值得說明的是,雖然utf編碼對漢字使用3個字節,但即使對於漢字網頁,utf編碼也會比 unicode編碼節省,因為網頁中包含了很多的英文字符。
3. java對字符的處理
在java應用軟件中,會有多處涉及到字符集編碼,有些地方需要進行正確的設置,有些地方需要進行一定程度的處理。
3.1getBytes(charset)
這是java字符串處理的一個標准函數,其作用是將字符串所表示的字符按照charset編碼,並以字節方式表示。注意字符串在java內存中總是按 unicode編碼存儲的。比如"中文",正常情況下(即沒有錯誤的時候)存儲為"4e2d 6587",如果charset為"gbk",則被編碼為 "d6d0 cec4",然後返回字節"d6 d0 ce c4"。如果charset為"utf8"則最後是"e4 b8 ad e6 96 87"。 如果是"iso8859-1",則由於無法編碼,最後返回 "3f 3f"(兩個問號)。
3.2new String(charset)
這是java字符串處理的另一個標准函數,和上一個函數的作用相反,將字節數組按照charset編碼進行組合識別,最後轉換為unicode存儲。參考上述getBytes的例子,"gbk" 和"utf8"都可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了 "003f 003f"(兩個問號)。
因為utf8可以用來表示/編碼所有字符,所以new String(str.getBytes("utf8"), "utf8") === str,即完全可逆。
3.3setCharacterEncoding()
該函數用來設置http請求或者相應的編碼。
對 於request,是指提交內容的編碼,指定後可以通過getParameter()則直接獲得正確的字符串,如果不指定,則默認使用iso8859-1 編碼,需要進一步處理。參見下述"表單輸入"。值得注意的是在執行setCharacterEncoding()之前,不能執行任何 getParameter()。java doc上說明: This method must be called prior to reading request parameters or reading input using getReader ()。而且,該指定只對POST方法有效,對GET方法無效。分析原因,應該是在執行第一個getParameter()的時候,java將會按照編碼分析所有的提交內容,而後續的getParameter()不再進行分析,所以setCharacterEncoding()無效。而對於GET方法提交表單是,提交的內容在URL中,一開始就已經按照編碼分析所有的提交內容,setCharacterEncoding()自然就無效。
對於response,則是指定輸出內容的編碼,同時,該設置會傳遞給浏覽器,告訴浏覽器輸出內容所采用的編碼。
3.4處理過程
下面分析兩個有代表性的例子,說明java對編碼有關問題的處理方法。
3.4.1 表單輸入
表單輸入編碼過程如下:
(1) User input *(gbk:d6d0 cec4)
(2) browser(gbk:d6d0 cec4)
(3) web server(iso8859-1:00d6 00d 000ce 00c4)
(4) class
需要在class中進行處理:getbytes("iso8859-1")為d6 d0 ce c4,new String ("gbk")為d6d0 cec4,內存中以unicode編碼則為4e2d 6587。
(1) 用戶輸入的編碼方式和頁面指定的編碼有關,也和用戶的操作系統有關,所以是不確定的,上例以gbk為例。
(2) 從browser到web server,可以在表單中指定提交內容時使用的字符集,否則會使用頁面指定的編碼。而如果在url中直接用?的方式輸入參數,則其編碼往往是操作系統本身的編碼,因為這時和頁面無關。上述仍舊以gbk編碼為例。
(3) Web server 接收到的是字節流,默認時(getParameter)會以iso8859-1編碼處理之,結果是不正確的,所以需要進行處理。但如果預先設置了編碼(通過request.setCharacterEncoding()),則能夠直接獲取到正確的結果。
(4) 在頁面中指定編碼是個好習慣,否則可能失去控制,無法指定正確的編碼。
3.4.2 文件編譯
假設文件是gbk編碼保存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的默認編碼,後者是linux的默認編碼,當然也可以在編譯時指定編碼。
編譯過程如下:
(1) Jsp * (gbk:d6d0 cec4)
(2) java file *(gbk:d6d0 cec4)
(3) compiler read uincode(gbk:4e2d 6587; iso8859-1:00d6 00d 000ce 00c4)
(4) compiler write utf(gbk:e4b8ad e69687; iso8859-1:*)
(5) compiled file unicode(gbk:4e2d 6587; iso8859-1:00d6 00d 000ce 00c4)
(6) class unicode(4e2d 6587)
(7) system.out / jsp.out gbk(d6d0 cec4)
(8) os console / browser
所以用gbk編碼保存,而用iso8859-1編譯的結果是不正確的。
(1) 文件可以以多種編碼方式保存,中文windows下,默認為ansi/gbk。
(2) 編譯器讀取文件時,需要得到文件的編碼,如果未指定,則使用系統默認編碼。一般class文件,是以系統默認編碼保存的,所以編譯不會出問題,但對於 jsp文件,如果在中文windows下編輯保存,而部署在英文linux下運行/編譯,則會出現問題。所以需要在jsp文件中用 pageEncoding指定編碼。
(3) Java編譯的時候會轉換成統一的unicode編碼處理,最後保存的時候再轉換為utf編碼。
(4) 當系統輸出字符的時候,會按指定編碼輸出,對於中文windows下,System.out將使用gbk編碼,而對於response(浏覽器),則使用 jsp文件頭指定的contentType,或者可以直接為response指定編碼。同時,會告訴browser網頁的編碼。如果未指定,則會使用 iso8859-1編碼。對於中文,應該為browser指定輸出字符串的編碼。
(5) browser顯示網頁的時候,首先使用response中指定的編碼(jsp文件頭指定的contentType最終也反映在response上),如果未指定,則會使用網頁中meta項指定中的contentType。
3.5幾處設置
對於web應用程序,和編碼有關的設置或者函數如下。
3.5.1 jsp編譯
指定文件的存儲編碼,很明顯,該設置應該置於文件的開頭。例如:<%@page pageEncoding="GBK"%>。另外,對於一般class文件,可以在編譯的時候指定編碼。
3.5.2 jsp輸出
指定文件輸出到browser是使用的編碼,該設置也應該置於文件的開頭。例如:<%@ page contentType="text/html; charset= GBK" %>。該設置和response.setCharacterEncoding("GBK")等效。
3.5.3 meta設置
指定網頁使用的編碼,該設置對靜態網頁尤其有作用。因為靜態網頁無法采用jsp的設置,而且也無法執行 response.setCharacterEncoding()。
例如:<META http-equiv="Content-Type" content="text/html; charset=GBK"/>
如果同時采用了jsp輸出和meta設置兩種編碼指定方式,則jsp指定的優先。因為jsp指定的直接體現在response中。
需要注意的是,apache有一個設置可以給無編碼指定的網頁指定編碼,該指定等同於jsp的編碼指定方式,所以會覆蓋靜態網頁中的meta指定。所以有人建議關閉該設置。
3.5.4 form設置
當浏覽器提交表單的時候,可以指定相應的編碼。例如:<form accept-charset="gb2312">。一般不必不使用該設置,浏覽器會直接使用網頁的編碼。