JAVA字符集
. 概述
本文主要包括以下幾個方面:編碼基本知識,java,系統軟體,url,工具軟體等。
在下面的描述中,將以"中文"兩個字為例,經查表可以知道其GB2312編碼是"d6d0 cec4",Unicode編碼為"4e2d 6587",UTF編碼就是"e4b8ad e69687"。注意,這兩個字沒有iso8859-1編碼,但可以用iso8859-1編碼來"表示"。
2. 編碼基本知識
最早的編碼是iso8859-1,和ascii編碼相似。但為了方便表示各種各樣的語言,逐漸出現了很多標準編碼,重要的有如下幾個。
2.1. iso8859-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.2. GB2312/GBK
這就是漢子的國標碼,專門用來表示漢字,是雙位元組編碼,而英文字母和iso8859-1一致(相容iso8859-1編碼)。其中gbk編碼能夠用來同時表示繁體字和簡體字,而gb2312只能表示簡體字,gbk是相容gb2312編碼的。
2.3. unicode
這是最統一的編碼,可以用來表示所有語言的字元,而且是定長雙位元組(也有四位元組的)編碼,包括英文字母在內。所以可以說它是不相容iso8859-1編碼的,也不相容任何編碼。不過,相對於iso8859-1編碼來說,uniocode編碼只是在前面增加了一個0位元組,比如字母a為"00 61"。
需要說明的是,定長編碼便於計算機處理(注意GB2312/GBK不是定長編碼),而unicode又可以用來表示所有字元,所以在很多軟體內部是使用unicode編碼來處理的,比如java。
2.4. UTF
考慮到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.1. getBytes(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.2. new String(charset)
這是java字串處理的另一個標準函式,和上一個函式的作用相反,將位元組陣列按照charset編碼進行組合識別,最後轉換為unicode儲存。參考上述getBytes的例子,"gbk" 和"utf8"都可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了"003f 003f"(兩個問號)。
因為utf8可以用來表示/編碼所有字元,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。
3.3. setCharacterEncoding()
該函式用來設定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. 表單輸入
User input *(gbk:d6d0 cec4) browser *(gbk:d6d0 cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中進行處理:getbytes("iso8859-1")為d6 d0 ce c4,new String("gbk")為d6d0 cec4,記憶體中以unicode編碼則為4e2d 6587。
l 使用者輸入的編碼方式和頁面指定的編碼有關,也和使用者的作業系統有關,所以是不確定的,上例以gbk為例。
l 從browser到web server,可以在表單中指定提交內容時使用的字符集,否則會使用頁面指定的編碼。而如果在url中直接用?的方式輸入引數,則其編碼往往是作業系統本身的編碼,因為這時和頁面無關。上述仍舊以gbk編碼為例。
l Web server接收到的是位元組流,預設時(getParameter)會以iso8859-1編碼處理之,結果是不正確的,所以需要進行處理。但如果預先設定了編碼(通過request. setCharacterEncoding ()),則能夠直接獲取到正確的結果。
l 在頁面中指定編碼是個好習慣,否則可能失去控制,無法指定正確的編碼。
3.4.2. 檔案編譯
假設檔案是gbk編碼儲存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的預設編碼,後者是linux的預設編碼,當然也可以在編譯時指定編碼。
Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) compiler write utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class。所以用gbk編碼儲存,而用iso8859-1編譯的結果是不正確的。
class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。
l 檔案可以以多種編碼方式儲存,中文windows下,預設為ansi/gbk。
l 編譯器讀取檔案時,需要得到檔案的編碼,如果未指定,則使用系統預設編碼。一般class檔案,是以系統預設編碼儲存的,所以編譯不會出問題,但對於jsp檔案,如果在中文windows下編輯儲存,而部署在英文linux下執行/編譯,則會出現問題。所以需要在jsp檔案中用pageEncoding指定編碼。
l Java編譯的時候會轉換成統一的unicode編碼處理,最後儲存的時候再轉換為utf編碼。
l 當系統輸出字元的時候,會按指定編碼輸出,對於中文windows下,System.out將使用gbk編碼,而對於response(瀏覽器),則使用jsp檔案頭指定的contentType,或者可以直接為response指定編碼。同時,會告訴browser網頁的編碼。如果未指定,則會使用iso8859-1編碼。對於中文,應該為browser指定輸出字串的編碼。
l browser顯示網頁的時候,首先使用response中指定的編碼(jsp檔案頭指定的contentType最終也反映在response上),如果未指定,則會使用網頁中meta項指定中的contentType。
3.5. 幾處設定
對於web應用程式,和編碼有關的設定或者函式如下。
3.5.1. jsp編譯
指定檔案的儲存編碼,很明顯,該設定應該置於檔案的開頭。例如:。另外,對於一般class檔案,可以在編譯的時候指定編碼。
3.5.2. jsp輸出
指定檔案輸出到browser是使用的編碼,該設定也應該置於檔案的開頭。例如:。該設定和response.setCharacterEncoding("GBK")等效。
3.5.3. meta設定
指定網頁使用的編碼,該設定對靜態網頁尤其有作用。因為靜態網頁無法採用jsp的設定,而且也無法執行response.setCharacterEncoding()。例如:
如果同時採用了jsp輸出和meta設定兩種編碼指定方式,則jsp指定的優先。因為jsp指定的直接體現在response中。
需要注意的是,apache有一個設定可以給無編碼指定的網頁指定編碼,該指定等同於jsp的編碼指定方式,所以會覆蓋靜態網頁中的meta指定。所以有人建議關閉該設定。
3.5.4. form設定
當瀏覽器提交表單的時候,可以指定相應的編碼。例如:
。一般不必不使用該設定,瀏覽器會直接使用網頁的編碼。
4. 系統軟體
下面討論幾個相關的系統軟體。
4.1. mysql資料庫
很明顯,要支援多語言,應該將資料庫的編碼設定成utf或者unicode,而utf更適合與儲存。但是,如果中文資料中包含的英文字母很少,其實unicode更為適合。
資料庫的編碼可以通過mysql的配置檔案設定,例如default-character-set=utf8。還可以在資料庫連結URL中設定,例如: useUnicode=true&characterEncoding=UTF-8。注意這兩者應該保持一致,在新的sql版本里,在資料庫連結URL裡可以不進行設定,但也不能是錯誤的設定。
4.2. apache
appache和編碼有關的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,該功能會將所有靜態頁面的編碼設定為UTF-8,最好關閉該功能。
另外,apache還有單獨的模組來處理網頁響應頭,其中也可能對編碼進行設定。
4.3. linux預設編碼
這裡所說的linux預設編碼,是指執行時的環境變數。兩個重要的環境變數是LC_ALL和LANG,預設編碼會影響到java URLEncode的行為,下面有描述。
建議都設定為"zh_CN.UTF-8"。
4.4. 其它
為了支援中文檔名,linux在載入磁碟時應該指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。
另外,如前所述,使用GET方法提交的資訊不支援request.setCharacterEncoding(),但可以通過tomcat的配置檔案指定字符集,在tomcat的server.xml檔案中,形如:。這種方法將統一設定所有請求,而不能針對具體頁面進行設定,也不一定和browser使用的編碼相同,所以有時候並不是所期望的。
5. URL地址
URL地址中含有中文字元是很麻煩的,前面描述過使用GET方法提交表單的情況,使用GET方法時,引數就是包含在URL中。
5.1. URL編碼
對於URL中的一些特殊字元,瀏覽器會自動進行編碼。這些字元除了"/?&"等外,還包括unicode字元,比如漢子。這時的編碼比較特殊。
IE有一個選項"總是使用UTF-8傳送URL",當該選項有效時,IE將會對特殊字元進行UTF-8編碼,同時進行URL編碼。如果改選項無效,則使用預設編碼"GBK",並且不進行URL編碼。但是,對於URL後面的引數,則總是不進行編碼,相當於UTF-8選項無效。比如"中文.html?a=中文",當UTF-8選項有效時,將傳送連結"%e4%b8%ad%e6%96%87.html?a=x4ex2dx65x87";而UTF-8選項無效時,將傳送連結"x4ex2dx65x87.html?a=x4ex2dx65x87"。注意後者前面的"中文"兩個字只有4個位元組,而前者卻有18個位元組,這主要時URL編碼的原因。
當web server(tomcat)接收到該連結時,將會進行URL解碼,即去掉"%",同時按照ISO8859-1編碼(上面已經描述,可以使用URLEncoding來設定成其它編碼)識別。上述例子的結果分別是"ue4ub8uadue6u96u87.html?a=u4eu2du65u87"和"u4eu2du65u87.html?a=u4eu2du65u87",注意前者前面的"中文"兩個字恢復成了6個字元。這裡用"u",表示是unicode。
所以,由於客戶端設定的不同,相同的連結,在伺服器上得到了不同結果。這個問題不少人都遇到,卻沒有很好的解決辦法。所以有的網站會建議使用者嘗試關閉UTF-8選項。不過,下面會描述一個更好的處理辦法。
5.2. rewrite
熟悉的人都知道,apache有一個功能強大的rewrite模組,這裡不描述其功能。需要說明的是該模組會自動將URL解碼(去除%),即完成上述web server(tomcat)的部分功能。有相關文件介紹說可以使用[NE]引數來關閉該功能,但我試驗並未成功,可能是因為版本(我使用的是apache 2.0.54)問題。另外,當引數中含有"?& "等符號的時候,該功能將導致系統得不到正常結果。
rewrite本身似乎完全是採用位元組處理的方式,而不考慮字串的編碼,所以不會帶來編碼問題。
5.3. URLEncode.encode()
這是Java本身提供對的URL編碼函式,完成的工作和上述UTF-8選項有效時瀏覽器所做的工作相似。值得說明的是,java已經不贊成不指定編碼來使用該方法(deprecated)。應該在使用的時候增加編碼指定。
當不指定編碼的時候,該方法使用系統預設編碼,這會導致軟體執行結果得不確定。比如對於"中文",當系統預設編碼為"gb2312"時,結果是"%4e%2d%65%87",而預設編碼為"UTF-8",結果卻是"%e4%b8%ad%e6%96%87",後續程式將難以處理。另外,這兒說的系統預設編碼是由執行tomcat時的環境變數LC_ALL和LANG等決定的,曾經出現過tomcat重啟後就出現亂碼的問題,最後才鬱悶的發現是因為修改修改了這兩個環境變數。
建議統一指定為"UTF-8"編碼,可能需要修改相應的程式。
5.4. 一個解決方案
上面說起過,因為瀏覽器設定的不同,對於同一個連結,web server收到的是不同內容,而軟體系統有無法知道這中間的區別,所以這一協議目前還存在缺陷。
針對具體問題,不應該僥倖認為所有客戶的IE設定都是UTF-8有效的,也不應該粗暴的建議使用者修改IE設定,要知道,使用者不可能去記住每一個web server的設定。所以,接下來的解決辦法就只能是讓自己的程式多一點智慧:根據內容來分析編碼是否UTF-8。
比較幸運的是UTF-8編碼相當有規律,所以可以通過分析傳輸過來的連結內容,來判斷是否是正確的UTF-8字元,如果是,則以UTF-8處理之,如果不是,則使用客戶預設編碼(比如"GBK"),下面是一個判斷是否UTF-8的例子,如果你瞭解相應規律,就容易理解。
public static boolean isValidUtf8(byte[] b,int aMaxCount){
int lLen=b.length,lCharCount=0;
for(int i=0;i
byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)
if(lByte>=0) continue;//>=0 is normal ascii
if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;
int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4
:lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;
if(i+lCount>lLen) return false;
for(int j=0;j=(byte)0xc0) return false;
}
return true;
}
相應地,一個使用上述方法的例子如下:
public static String getUrlParam(String aStr,String aDefaultCharset)
throws UnsupportedEncodingException{
if(aStr==null) return null;
byte[] lBytes=aStr.getBytes("ISO-8859-1");
return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);
}
不過,該方法也存在缺陷,如下兩方面:
l 沒有包括對使用者預設編碼的識別,這可以根據請求資訊的語言來判斷,但不一定正確,因為我們有時候也會輸入一些韓文,或者其他文字。
l 可能會錯誤判斷UTF-8字元,一個例子是"學習"兩個字,其GBK編碼是" xd1xa7xcfxb0",如果使用上述isValidUtf8方法判斷,將返回true。可以考慮使用更嚴格的判斷方法,不過估計效果不大。
有一個例子可以證明google也遇到了上述問題,而且也採用了和上述相似的處理方法,比如,如果在位址列中輸入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=學習",google將無法正確識別,而其他漢字一般能夠正常識別。
最後,應該補充說明一下,如果不使用rewrite規則,或者通過表單提交資料,其實並不一定會遇到上述問題,因為這時可以在提交資料時指定希望的編碼。另外,中文檔名確實會帶來問題,應該謹慎使用。
6. 其它
下面描述一些和編碼有關的其他問題。
6.1. SecureCRT
除了瀏覽器和控制檯與編碼有關外,一些客戶端也很有關係。比如在使用SecureCRT連線linux時,應該讓SecureCRT的顯示編碼(不同的session,可以有不同的編碼設定)和linux的編碼環境變數保持一致。否則看到的一些幫助資訊,就可能是亂碼。
另外,mysql有自己的編碼設定,也應該保持和SecureCRT的顯示編碼一致。否則通過SecureCRT執行sql語句的時候,可能無法處理中文字元,查詢結果也會出現亂碼。
對於Utf-8檔案,很多編輯器(比如記事本)會在檔案開頭增加三個不可見的標誌位元組,如果作為mysql的輸入檔案,則必須要去掉這三個字元。(用linux的vi儲存可以去掉這三個字元)。一個有趣的現象是,在中文windows下,建立一個新txt檔案,用記事本開啟,輸入"連通"兩個字,儲存,再開啟,你會發現兩個字沒了,只留下一個小黑點。
6.2. 過濾器
如果需要統一設定編碼,則通過filter進行設定是個不錯的選擇。在filter class中,可以統一為需要的請求或者回應設定編碼。參加上述setCharacterEncoding()。這個類apache已經給出了可以直接使用的例子SetCharacterEncodingFilter。
6.3. POST和GET
很明顯,以POST提交資訊時,URL有更好的可讀性,而且可以方便的使用setCharacterEncoding()來處理字符集問題。但GET方法形成的URL能夠更容易表達網頁的實際內容,也能夠用於收藏。
從統一的角度考慮問題,建議採用GET方法,這要求在程式中獲得引數是進行特殊處理,而無法使用setCharacterEncoding()的便利,如果不考慮rewrite,就不存在IE的UTF-8問題,可以考慮通過設定URIEncoding來方便獲取URL中的引數。
6.4. 簡繁體編碼轉換
GBK同時包含簡體和繁體編碼,也就是說同一個字,由於編碼不同,在GBK編碼下屬於兩個字。有時候,為了正確取得完整的結果,應該將繁體和簡體進行統一。可以考慮將UTF、GBK中的所有繁體字,轉換為相應的簡體字,BIG5編碼的資料,也應該轉化成相應的簡體字。當然,仍舊以UTF編碼儲存。
例如,對於"語言 ?言",用UTF表示為"xE8xAFxADxE8xA8x80 xE8xAAx9ExE8xA8x80",進行簡繁體編碼轉換後應該是兩個相同的 "xE8xAFxADxE8xA8x80>"。
本文主要包括以下幾個方面:編碼基本知識,java,系統軟體,url,工具軟體等。
在下面的描述中,將以"中文"兩個字為例,經查表可以知道其GB2312編碼是"d6d0 cec4",Unicode編碼為"4e2d 6587",UTF編碼就是"e4b8ad e69687"。注意,這兩個字沒有iso8859-1編碼,但可以用iso8859-1編碼來"表示"。
2. 編碼基本知識
最早的編碼是iso8859-1,和ascii編碼相似。但為了方便表示各種各樣的語言,逐漸出現了很多標準編碼,重要的有如下幾個。
2.1. iso8859-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.2. GB2312/GBK
這就是漢子的國標碼,專門用來表示漢字,是雙位元組編碼,而英文字母和iso8859-1一致(相容iso8859-1編碼)。其中gbk編碼能夠用來同時表示繁體字和簡體字,而gb2312只能表示簡體字,gbk是相容gb2312編碼的。
2.3. unicode
這是最統一的編碼,可以用來表示所有語言的字元,而且是定長雙位元組(也有四位元組的)編碼,包括英文字母在內。所以可以說它是不相容iso8859-1編碼的,也不相容任何編碼。不過,相對於iso8859-1編碼來說,uniocode編碼只是在前面增加了一個0位元組,比如字母a為"00 61"。
需要說明的是,定長編碼便於計算機處理(注意GB2312/GBK不是定長編碼),而unicode又可以用來表示所有字元,所以在很多軟體內部是使用unicode編碼來處理的,比如java。
2.4. UTF
考慮到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.1. getBytes(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.2. new String(charset)
這是java字串處理的另一個標準函式,和上一個函式的作用相反,將位元組陣列按照charset編碼進行組合識別,最後轉換為unicode儲存。參考上述getBytes的例子,"gbk" 和"utf8"都可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了"003f 003f"(兩個問號)。
因為utf8可以用來表示/編碼所有字元,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。
3.3. setCharacterEncoding()
該函式用來設定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. 表單輸入
User input *(gbk:d6d0 cec4) browser *(gbk:d6d0 cec4) web server iso8859-1(00d6 00d 000ce 00c4) class,需要在class中進行處理:getbytes("iso8859-1")為d6 d0 ce c4,new String("gbk")為d6d0 cec4,記憶體中以unicode編碼則為4e2d 6587。
l 使用者輸入的編碼方式和頁面指定的編碼有關,也和使用者的作業系統有關,所以是不確定的,上例以gbk為例。
l 從browser到web server,可以在表單中指定提交內容時使用的字符集,否則會使用頁面指定的編碼。而如果在url中直接用?的方式輸入引數,則其編碼往往是作業系統本身的編碼,因為這時和頁面無關。上述仍舊以gbk編碼為例。
l Web server接收到的是位元組流,預設時(getParameter)會以iso8859-1編碼處理之,結果是不正確的,所以需要進行處理。但如果預先設定了編碼(通過request. setCharacterEncoding ()),則能夠直接獲取到正確的結果。
l 在頁面中指定編碼是個好習慣,否則可能失去控制,無法指定正確的編碼。
3.4.2. 檔案編譯
假設檔案是gbk編碼儲存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的預設編碼,後者是linux的預設編碼,當然也可以在編譯時指定編碼。
Jsp *(gbk:d6d0 cec4) java file *(gbk:d6d0 cec4) compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) compiler write utf(gbk: e4b8ad e69687; iso8859-1: *) compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class。所以用gbk編碼儲存,而用iso8859-1編譯的結果是不正確的。
class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。
l 檔案可以以多種編碼方式儲存,中文windows下,預設為ansi/gbk。
l 編譯器讀取檔案時,需要得到檔案的編碼,如果未指定,則使用系統預設編碼。一般class檔案,是以系統預設編碼儲存的,所以編譯不會出問題,但對於jsp檔案,如果在中文windows下編輯儲存,而部署在英文linux下執行/編譯,則會出現問題。所以需要在jsp檔案中用pageEncoding指定編碼。
l Java編譯的時候會轉換成統一的unicode編碼處理,最後儲存的時候再轉換為utf編碼。
l 當系統輸出字元的時候,會按指定編碼輸出,對於中文windows下,System.out將使用gbk編碼,而對於response(瀏覽器),則使用jsp檔案頭指定的contentType,或者可以直接為response指定編碼。同時,會告訴browser網頁的編碼。如果未指定,則會使用iso8859-1編碼。對於中文,應該為browser指定輸出字串的編碼。
l browser顯示網頁的時候,首先使用response中指定的編碼(jsp檔案頭指定的contentType最終也反映在response上),如果未指定,則會使用網頁中meta項指定中的contentType。
3.5. 幾處設定
對於web應用程式,和編碼有關的設定或者函式如下。
3.5.1. jsp編譯
指定檔案的儲存編碼,很明顯,該設定應該置於檔案的開頭。例如:。另外,對於一般class檔案,可以在編譯的時候指定編碼。
3.5.2. jsp輸出
指定檔案輸出到browser是使用的編碼,該設定也應該置於檔案的開頭。例如:。該設定和response.setCharacterEncoding("GBK")等效。
3.5.3. meta設定
指定網頁使用的編碼,該設定對靜態網頁尤其有作用。因為靜態網頁無法採用jsp的設定,而且也無法執行response.setCharacterEncoding()。例如:
如果同時採用了jsp輸出和meta設定兩種編碼指定方式,則jsp指定的優先。因為jsp指定的直接體現在response中。
需要注意的是,apache有一個設定可以給無編碼指定的網頁指定編碼,該指定等同於jsp的編碼指定方式,所以會覆蓋靜態網頁中的meta指定。所以有人建議關閉該設定。
3.5.4. form設定
當瀏覽器提交表單的時候,可以指定相應的編碼。例如:
。一般不必不使用該設定,瀏覽器會直接使用網頁的編碼。
4. 系統軟體
下面討論幾個相關的系統軟體。
4.1. mysql資料庫
很明顯,要支援多語言,應該將資料庫的編碼設定成utf或者unicode,而utf更適合與儲存。但是,如果中文資料中包含的英文字母很少,其實unicode更為適合。
資料庫的編碼可以通過mysql的配置檔案設定,例如default-character-set=utf8。還可以在資料庫連結URL中設定,例如: useUnicode=true&characterEncoding=UTF-8。注意這兩者應該保持一致,在新的sql版本里,在資料庫連結URL裡可以不進行設定,但也不能是錯誤的設定。
4.2. apache
appache和編碼有關的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,該功能會將所有靜態頁面的編碼設定為UTF-8,最好關閉該功能。
另外,apache還有單獨的模組來處理網頁響應頭,其中也可能對編碼進行設定。
4.3. linux預設編碼
這裡所說的linux預設編碼,是指執行時的環境變數。兩個重要的環境變數是LC_ALL和LANG,預設編碼會影響到java URLEncode的行為,下面有描述。
建議都設定為"zh_CN.UTF-8"。
4.4. 其它
為了支援中文檔名,linux在載入磁碟時應該指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。
另外,如前所述,使用GET方法提交的資訊不支援request.setCharacterEncoding(),但可以通過tomcat的配置檔案指定字符集,在tomcat的server.xml檔案中,形如:。這種方法將統一設定所有請求,而不能針對具體頁面進行設定,也不一定和browser使用的編碼相同,所以有時候並不是所期望的。
5. URL地址
URL地址中含有中文字元是很麻煩的,前面描述過使用GET方法提交表單的情況,使用GET方法時,引數就是包含在URL中。
5.1. URL編碼
對於URL中的一些特殊字元,瀏覽器會自動進行編碼。這些字元除了"/?&"等外,還包括unicode字元,比如漢子。這時的編碼比較特殊。
IE有一個選項"總是使用UTF-8傳送URL",當該選項有效時,IE將會對特殊字元進行UTF-8編碼,同時進行URL編碼。如果改選項無效,則使用預設編碼"GBK",並且不進行URL編碼。但是,對於URL後面的引數,則總是不進行編碼,相當於UTF-8選項無效。比如"中文.html?a=中文",當UTF-8選項有效時,將傳送連結"%e4%b8%ad%e6%96%87.html?a=x4ex2dx65x87";而UTF-8選項無效時,將傳送連結"x4ex2dx65x87.html?a=x4ex2dx65x87"。注意後者前面的"中文"兩個字只有4個位元組,而前者卻有18個位元組,這主要時URL編碼的原因。
當web server(tomcat)接收到該連結時,將會進行URL解碼,即去掉"%",同時按照ISO8859-1編碼(上面已經描述,可以使用URLEncoding來設定成其它編碼)識別。上述例子的結果分別是"ue4ub8uadue6u96u87.html?a=u4eu2du65u87"和"u4eu2du65u87.html?a=u4eu2du65u87",注意前者前面的"中文"兩個字恢復成了6個字元。這裡用"u",表示是unicode。
所以,由於客戶端設定的不同,相同的連結,在伺服器上得到了不同結果。這個問題不少人都遇到,卻沒有很好的解決辦法。所以有的網站會建議使用者嘗試關閉UTF-8選項。不過,下面會描述一個更好的處理辦法。
5.2. rewrite
熟悉的人都知道,apache有一個功能強大的rewrite模組,這裡不描述其功能。需要說明的是該模組會自動將URL解碼(去除%),即完成上述web server(tomcat)的部分功能。有相關文件介紹說可以使用[NE]引數來關閉該功能,但我試驗並未成功,可能是因為版本(我使用的是apache 2.0.54)問題。另外,當引數中含有"?& "等符號的時候,該功能將導致系統得不到正常結果。
rewrite本身似乎完全是採用位元組處理的方式,而不考慮字串的編碼,所以不會帶來編碼問題。
5.3. URLEncode.encode()
這是Java本身提供對的URL編碼函式,完成的工作和上述UTF-8選項有效時瀏覽器所做的工作相似。值得說明的是,java已經不贊成不指定編碼來使用該方法(deprecated)。應該在使用的時候增加編碼指定。
當不指定編碼的時候,該方法使用系統預設編碼,這會導致軟體執行結果得不確定。比如對於"中文",當系統預設編碼為"gb2312"時,結果是"%4e%2d%65%87",而預設編碼為"UTF-8",結果卻是"%e4%b8%ad%e6%96%87",後續程式將難以處理。另外,這兒說的系統預設編碼是由執行tomcat時的環境變數LC_ALL和LANG等決定的,曾經出現過tomcat重啟後就出現亂碼的問題,最後才鬱悶的發現是因為修改修改了這兩個環境變數。
建議統一指定為"UTF-8"編碼,可能需要修改相應的程式。
5.4. 一個解決方案
上面說起過,因為瀏覽器設定的不同,對於同一個連結,web server收到的是不同內容,而軟體系統有無法知道這中間的區別,所以這一協議目前還存在缺陷。
針對具體問題,不應該僥倖認為所有客戶的IE設定都是UTF-8有效的,也不應該粗暴的建議使用者修改IE設定,要知道,使用者不可能去記住每一個web server的設定。所以,接下來的解決辦法就只能是讓自己的程式多一點智慧:根據內容來分析編碼是否UTF-8。
比較幸運的是UTF-8編碼相當有規律,所以可以通過分析傳輸過來的連結內容,來判斷是否是正確的UTF-8字元,如果是,則以UTF-8處理之,如果不是,則使用客戶預設編碼(比如"GBK"),下面是一個判斷是否UTF-8的例子,如果你瞭解相應規律,就容易理解。
public static boolean isValidUtf8(byte[] b,int aMaxCount){
int lLen=b.length,lCharCount=0;
for(int i=0;i
byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;)
if(lByte>=0) continue;//>=0 is normal ascii
if(lByte<(byte)0xc0 || lByte>(byte)0xfd) return false;
int lCount=lByte>(byte)0xfc?5:lByte>(byte)0xf8?4
:lByte>(byte)0xf0?3:lByte>(byte)0xe0?2:1;
if(i+lCount>lLen) return false;
for(int j=0;j=(byte)0xc0) return false;
}
return true;
}
相應地,一個使用上述方法的例子如下:
public static String getUrlParam(String aStr,String aDefaultCharset)
throws UnsupportedEncodingException{
if(aStr==null) return null;
byte[] lBytes=aStr.getBytes("ISO-8859-1");
return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset);
}
不過,該方法也存在缺陷,如下兩方面:
l 沒有包括對使用者預設編碼的識別,這可以根據請求資訊的語言來判斷,但不一定正確,因為我們有時候也會輸入一些韓文,或者其他文字。
l 可能會錯誤判斷UTF-8字元,一個例子是"學習"兩個字,其GBK編碼是" xd1xa7xcfxb0",如果使用上述isValidUtf8方法判斷,將返回true。可以考慮使用更嚴格的判斷方法,不過估計效果不大。
有一個例子可以證明google也遇到了上述問題,而且也採用了和上述相似的處理方法,比如,如果在位址列中輸入"http://www.google.com/search?hl=zh-CN&newwindow=1&q=學習",google將無法正確識別,而其他漢字一般能夠正常識別。
最後,應該補充說明一下,如果不使用rewrite規則,或者通過表單提交資料,其實並不一定會遇到上述問題,因為這時可以在提交資料時指定希望的編碼。另外,中文檔名確實會帶來問題,應該謹慎使用。
6. 其它
下面描述一些和編碼有關的其他問題。
6.1. SecureCRT
除了瀏覽器和控制檯與編碼有關外,一些客戶端也很有關係。比如在使用SecureCRT連線linux時,應該讓SecureCRT的顯示編碼(不同的session,可以有不同的編碼設定)和linux的編碼環境變數保持一致。否則看到的一些幫助資訊,就可能是亂碼。
另外,mysql有自己的編碼設定,也應該保持和SecureCRT的顯示編碼一致。否則通過SecureCRT執行sql語句的時候,可能無法處理中文字元,查詢結果也會出現亂碼。
對於Utf-8檔案,很多編輯器(比如記事本)會在檔案開頭增加三個不可見的標誌位元組,如果作為mysql的輸入檔案,則必須要去掉這三個字元。(用linux的vi儲存可以去掉這三個字元)。一個有趣的現象是,在中文windows下,建立一個新txt檔案,用記事本開啟,輸入"連通"兩個字,儲存,再開啟,你會發現兩個字沒了,只留下一個小黑點。
6.2. 過濾器
如果需要統一設定編碼,則通過filter進行設定是個不錯的選擇。在filter class中,可以統一為需要的請求或者回應設定編碼。參加上述setCharacterEncoding()。這個類apache已經給出了可以直接使用的例子SetCharacterEncodingFilter。
6.3. POST和GET
很明顯,以POST提交資訊時,URL有更好的可讀性,而且可以方便的使用setCharacterEncoding()來處理字符集問題。但GET方法形成的URL能夠更容易表達網頁的實際內容,也能夠用於收藏。
從統一的角度考慮問題,建議採用GET方法,這要求在程式中獲得引數是進行特殊處理,而無法使用setCharacterEncoding()的便利,如果不考慮rewrite,就不存在IE的UTF-8問題,可以考慮通過設定URIEncoding來方便獲取URL中的引數。
6.4. 簡繁體編碼轉換
GBK同時包含簡體和繁體編碼,也就是說同一個字,由於編碼不同,在GBK編碼下屬於兩個字。有時候,為了正確取得完整的結果,應該將繁體和簡體進行統一。可以考慮將UTF、GBK中的所有繁體字,轉換為相應的簡體字,BIG5編碼的資料,也應該轉化成相應的簡體字。當然,仍舊以UTF編碼儲存。
例如,對於"語言 ?言",用UTF表示為"xE8xAFxADxE8xA8x80 xE8xAAx9ExE8xA8x80",進行簡繁體編碼轉換後應該是兩個相同的 "xE8xAFxADxE8xA8x80>"。
相關文章
- JAVA java學習(22)——————Eclipse 修改字符集JavaEclipse
- 教妹學Java(十):Unicode字符集簡介JavaUnicode
- 字符集
- MySQL字符集MySql
- Tomcat字符集Tomcat
- 38、字符集_2(匯出匯入指定字符集)
- 深入理解Emoji(一) —— 字符集,字符集編碼
- Java 18將指定UTF-8作為標準Java API的預設字符集JavaAPI
- 更新Linux字符集Linux
- Oracle 字符集修改Oracle
- 修改sqlserver字符集SQLServer
- 批次修改欄位字符集和表表字符集,sql生成SQL
- Java 18為什麼要指定UTF-8為預設字符集Java
- 字符集與編碼
- 4.2.1.4 選擇字符集
- mysql字符集說明MySql
- 更改Oracle字符集:把字符集ZHS16GBK換成UTF8Oracle
- weblogic 啟動指定字符集Web
- 字符集編碼(三):UnicodeUnicode
- 字符集編碼(四):UTF
- mysql字符集和字元排序MySql字元排序
- CentOS7.5修改字符集CentOS
- 聊一聊MySQL的字符集MySql
- 教你玩轉Eclipse—修改字符集Eclipse
- 字符集和比較規則
- MySQL 不同版本預設字符集MySql
- 2.2.2 關於字符集選擇
- 字符集編碼(上):Unicode 之前Unicode
- windows核心程式設計--字符集Windows程式設計
- 不同字符集倒庫的方法
- iOS CharacterSet(字符集)簡單理解iOS
- 資料型別和字符集資料型別
- VS2013 由Unicode字符集切換為多位元組字符集後編譯報錯Unicode編譯
- Mysql之儲存引擎及字符集MySql儲存引擎
- XSS和字符集的那些事兒
- 學習隨筆——GBK字符集——2020.11.4
- mysql 字符集造成的效能問題MySql
- 修改Oracle資料庫字符集(zt)Oracle資料庫
- 修改Oracle字符集為ZHS16GBKOracle