jsp教程頁面編碼問題分析
<%@page contenttype="text/html; charset=utf-8"%>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body>
中國
</body>
</html>
這個頁面在為什麼在啟動並執行時候“中國”會變成亂碼?
analysis
key step
對於上面問題的分析需要從整個jsp頁面請求的生命週期來看,一般的都需要經曆下面幾個階段:
1。應用伺服器根據jsp頁面產生一個java檔案
2。應用伺服器調用java.exe將java檔案編譯成一個servlet對應的class檔案
3。使用者的瀏覽器請求jsp對應的servlet,web容器起一個線程執行servlet,將資料返回給用戶端瀏覽器
4。使用者的ie根據返回的資料,將結果顯示給使用者。
key step analysis
為了更好的瞭解編碼問題,我們暫時先從上面的四個環節一步步來分析,根據分析的結果,來得到最終的解決辦法。
1. 在應用伺服器根據jsp頁面產生java檔案階段。
應用伺服器會將整個jsp頁面的代碼讀取出來,然後寫到一個新的java檔案中,在讀檔案和寫檔案的時候都牽涉到一個編碼問題,這個編碼問題應用伺服器是如何解決的呢?我研究tomcat應用伺服器的原始碼,發現tomcat中有一個pageencoding參數非常重要,在parsercontroller會從jsp檔案中讀出這個參數(如果沒有讀到,就從第一行的 contenttype中讀取charset),然後儲存起來,如果沒有讀取到這個參數,會從jspconfig中讀出一個預設的 pageencoding參數,如果這兩個參數都沒有的設定,系統會預設成iso8859-1的編碼來讀取原來的jsp檔案。
從上面的分析出,我們已經基本瞭解了應用伺服器讀取jsp檔案的編碼方式,由於java底層都是基於unicode編碼來儲存字元的,所以在寫檔案的時候,都輸出成unicode編碼的形式。
2。在jdk將java檔案編譯成class檔案的時候
可以利用-encoding參數指定源檔案的編碼,這在手動編譯的時候非常重要,因為這決定了java虛擬機器讀取java檔案時採用的編碼方式,但是在web應用中這個環節我們可以忽略,因為應用伺服器可以很好的解決這個編碼。以 tomcat為例,由於產生的java檔案是固定的utf-8編碼,所以tomcat也固定的採用utf-8編碼來讀取,通過瀏覽 abstractcatalinatask可以看到reader = new inputstreamreader(hconn.getinputstream(), charset);其中的charset=utf-8。所以在這個環節中應用伺服器都可以很好的把握,不會帶來編碼問題。
3. 使用者的瀏覽器請求jsp對應的servlet階段。
如果前面的環節中不會帶來編碼問題,也就是說在java虛擬機器中啟動並執行時候,能正常的擷取到“中國”,那麼在執行servlet的環節中不會“中國”始終是以unicode儲存的中國,那麼在第三個環節中需要關注的是 jspwriter如何將資料返回給用戶端瀏覽器。大家可以實驗一下,在java中如果用new string(str.getbytes("encoding"),"encoding")執行的時候,始終不會出現亂碼問題,也就是說,一個字串可以用不同的代碼來getbytes()產生位元組數組(底層i18n.jar所作的工作,提供byte2char和char2byte的轉換)。
如果大家可以理解這一點,那麼下面大家就需要瞭解jspwriter輸出字串時採用的編碼方式是什嗎?通過瀏覽response.java類可以瞭解到 tomcat應用伺服器是根據contenttype來擷取的writer的編碼方式,也就是說,最後返回用戶端的位元組流是contenttype對應的 charset中擷取出來的位元組數組。
4. ie根據返回的資料處理顯示階段
通過前面的分析可以瞭解到,應用伺服器返回的“中國”是根據 contenttype中的charset來顯示的,只要ie知道該用這個編碼來接收位元組流並轉成字串,並將使用者的瀏覽器推薦合適的編碼來查看結果,使用者就可以瀏覽到正確的“中國”兩個字。可以高興得是,目前的ie等瀏覽器正式這樣處理的。
conclusion
通過上面的分析,我們可以看到,在整個jsp頁面的編碼過程中,我們真正要解決的是jsp檔案到java檔案這個過程中的編碼問題,也就是pageencoding參數的設定問題。由於pageencoding參數是 servlet2.3規範中規定的參數,所以下面的方法在很多應用伺服器下面都通用,這方面的設定本人在工作中基本上得到了下面的一些方法:
1。在jsp頁面的中加上pageencoding參數,比如:<%@ page contenttype="text/html; charset=utf-8" pageencoding="gbk"%>,這樣就可以將頁面可以用ansi來儲存。也就是說當頁面儲存的編碼方式和chtenttype中的 charset不一樣的時候,可以考慮加上pageencoding參數。
2。有些應用伺服器(如weblogic),在沒有擷取到pageencoding參數的時候,不是先從charset中擷取編碼類別型,而是從另外的一些設定檔,如weblogic.xml檔案中加上下面的代碼:
<jsp-descriptor>
<jsp-param>
<param-name>compilersupports</param-name>
<param-value>true</param-value>
</jsp-param>
<jsp-param>
<param-name>encoding</param-name>
<param-value>gbk</param-value>
</jsp-param>
</jsp-descriptor>
(在tomcat5x種也有類似的處理,在應用的web.xml檔案中加上類似下面的配置項)
</jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
常用編碼方式分解
gb2312
中華人民共和國國家漢字資訊交換用編碼,全稱《資訊交換用漢字編碼字元集——基本集》,由國家標準總局發布,1981年5月1日實施,通行於大陸。新加坡等地也使用此編碼。,gb2312-80 是在國內電腦漢字資訊技術發展初始階段制定的,其中包含了大部分常用的一、二級漢字,和 9 區的符號。該字元集是幾乎所有的中文系統和國際化的軟體都支援的中文字元集,這也是最基本的中文字元集。其編碼範圍是高位0xa1-0xfe,低位也是 0xa1-0xfe;漢字從 0xb0a1 開始,結束於 0xf7fe;大約包含6000多漢字(不包括特殊字元).
gbk
是 gb2312-80 的擴充,是向上相容的。其編碼範圍是 0x8140-0xfefe,剔除高位 0x80 的字位。其所有字元都可以一對一映射到 unicode 2.0,也就是說 java 實際上提供了 gbk 字元集的支援。這是現階段 windows 和其它一些中文作業系統的預設字元集,但並不是所有的國際化軟體都支援該字元集,感覺是他們並不完全知道 gbk 是怎麼回事。值得注意的是它不是國家標準,而只是規範。隨著 gb18030-2000國標的發布,它將在不久的將來完成它的曆史使命。
gbk編碼是中國大陸制訂的、等同於ucs的新的中文編碼擴充國家標準。容納(包含特殊字元)共22014個字元編碼
unicode
採用16位編碼體系,其字元集內容與iso10646的bmp(basic multilingual plane)相同。unicode於1992年6月通過dis(draf international standard),目前版本v2.0於1996公布,內容包含符號6811個,漢字20902個,韓文拼音11172個,造字區6400個,保留 20249個,共計65534個。
utf-8
俗稱萬國碼,致力於使用統一的編碼準則表達各國的文字。為表達更多的文字,utf-8採用2/3混編的方式。目前容納的漢字範圍小於gbk編碼。並且以3位元組的方式處理中文,帶來了相容性的問題,原有的gbk,gb2312,gb18030編碼檔案都不能正常的處理。因其變寬編碼,使編程變成變得困難和複雜,因為即使是最基本的字元處理函數也要分別檢查每一位元組,以分辨字元邊界。這就降低了處理速度,並需要複雜、易錯的代碼。
附:相關編碼方式及關係
unicode:
unicode.org制定的編碼機制, 要將全世界常用文字都函括進去.
在1.0中是16位編碼, 由u+0000到u+ffff. 每個2byte碼對應一個字元; 在2.0開始拋棄了16位限制, 原來的16位作為基本位平面, 另外增加了16個位平面, 相當於20位編碼, 編碼範圍0到0x10ffff.
ucs:
iso制定的iso10646標準所定義的 universal character set, 採用4byte編碼.
unicode與ucs的關係:
iso與unicode.org是兩個不同的組織, 因此最初制定了不同的標準; 但自從unicode2.0開始, unicode採用了與iso 10646-1相同的字型檔和字碼, iso也承諾iso10646將不會給超出0x10ffff的ucs-4編碼賦值, 使得兩者保持一致.
ucs的編碼方式:
ucs-2, 與unicode的2byte編碼基本一樣.
ucs-4, 4byte編碼, 目前是在ucs-2前加上2個全零的byte.
utf: unicode/ucs transformation format
utf-8, 8bit編碼, ascii不作變換, 其他字元做變長編碼, 每個字元1-3 byte. 通常作為外碼. 有以下優點:
* 與cpu位元組順序無關, 可以在不同平台之間交流
* 容錯能力高, 任何一個位元組損壞後, 最多隻會導致一個編碼碼位損失, 不會鏈鎖錯誤(如gb碼錯一個位元組就會整行亂碼)
utf-16, 16bit編碼, 是變長碼, 大致相當於20位編碼, 值在0到0x10ffff之間, 基本上就是unicode編碼的實現. 它是變長碼, 與cpu字序有關, 但因為最省空間, 常作為網路傳輸的外碼.
utf-16是unicode的preferred encoding.
utf-32, 僅使用了unicode範圍(0到0x10ffff)的32位編碼, 相當於ucs-4的子集.
utf與unicode的關係:
unicode是一個字元集, 可以看作為內碼.
而utf是一種編碼方式, 它的出現是因為unicode不適宜在某些場合直接傳輸和處理. utf-16直接就是unicode編碼, 沒有變換, 但它包含了0x00在編碼內, 頭256位元組碼的第一個byte都是0x00, 在作業系統(c語言)中有特殊意義, 會引起問題. 採用utf-8編碼對unicode的直接編碼作些變換可以避免這問題, 並帶來一些優點.