在進行web開發的時候經常會遇到亂碼的問題,亂碼一般出現在:
1.寫在jsp檔案中的中文變成亂碼
2.頁面的中文都變成亂碼
3.後台通過request.getParameter()亂碼 編碼的基礎知識
電腦只能以位元組為單位儲存和傳輸資訊,而人需要看的是字串,位元組和字串之間的對應關係就是字元集,例如字元“中”使用UTF-8字元集映射的位元組為:E4 B8 AD 三個位元組,同樣反過來,這三個位元組通過UTF-8字元集映射便能得到“中”這個字元,不同的字元集映射規則不一樣,能表示的範圍也不一樣,例如“中”在GB2312中對應的位元組表示為:D6 D0 兩個位元組,字元和位元組之間的轉換,描述為編碼和解碼:
l 字元->位元組:編碼,例如:“中”的UTF-8編碼為 E4 B8 AD
l 位元組->字元:解碼,例如:位元組數組D0 D6根據GB2312解碼為字元“中”
還有一類編碼稱為URI編碼和URI解碼,不過URI編碼和解碼不是字串和位元組流之間的轉換,而是由一個字串表示另一個字串,例如:
l “中”的UTF-8 URI編碼為 %E4%B8%AD
l 字串%E4%B8%AD根據UTF-8進行URI解碼為字元“中”
可以看出來,URI編碼就是將一個字串用%+對應字元集的編碼組織的字串來表示的,在java中String類有兩個常用的方法進行編碼和解碼:
l getBytes :例如“中”.getBytes(“字元集”),根據指定的字元集進行編碼
l String(bytes[],”字元集”):根據自定的字元集對位元組數組進行解碼 為什麼會出現亂碼問題
瀏覽器和伺服器通過網路相連,瀏覽器請求編碼成位元組流在網路上傳輸,應用伺服器接收到瀏覽器發送過來的位元組流後通過相應的字元集再解碼為字串,如果瀏覽器和伺服器端使用了不同的字元集或者不相容的字元集就會導致亂碼問題,例如,瀏覽器將“中”按照UTF-8編碼為位元組E4 B8 AD,在網路上傳輸,應用伺服器接收到位元組後按照GBK進行解碼,那麼前兩個位元組首先被解碼為“涓”了,這隻是個簡單的過程說明,實際的過程比這要複雜。
因此要搞清web的編碼問題,很有必要先弄清請求的過程和過程中的編碼和解碼。web是請求響應模式,使用者操作瀏覽器,例如點擊一個按鈕提交表單,或者點擊一個超連結,這時瀏覽器就會嚮應用伺服器發送請求,servlet容器接受到請求後,根據web.xml的設定調用相應的應用程式,應用程式根據發送的請求進行一定的邏輯處理後返回給瀏覽器一段html代碼,瀏覽器根據html解析並展現給使用者,這就是一次請求應答的過程,下面對各部分作更加詳細的介紹: 1. 瀏覽器嚮應用伺服器發送請求
瀏覽器嚮應用伺服器發送請求一般通過三種方式:1.提交表單,2.超連結,3.Ajax;
1)表單提交
表單提交又分為post和get兩種方法,
採用post方法時瀏覽器會將表單中的字串,採用頁面的字元集編碼為位元組流發送到伺服器,
採用get方法時,瀏覽器首先會將表單中的值通過頁面的字元集進行URI編碼後拼接到action的URL後面發送到應用伺服器,例如:
|
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>test</title>
</head>
<body>
<form action=http://www.google.com>
<input type=text name=test value="中" />
<input type=submit />
</form>
</body>
</html> |
在點擊提交的時候,URL為 www.google.com.hk/?test=%E4%B8%AD,可以看出“中”字被編碼為%E4%B8%AD,因為當前頁面的字元集為UTF-8,所以在進行get表單提交時將按照UTF-8字元集進行URI編碼。
2)超連結
在超連結中一般會傳遞參數,有時候也會傳遞中文,如這段代碼:<a href="http://www.google.com/?test=中">link</a>,瀏覽器最終發送給伺服器的都是asc字元,其中的“中”不屬於asc字元集,因此也會對其進行URI編碼的,但是不同的瀏覽器採用的字元集卻不一樣,如上面的這個超連結片段,在windows7中,不論頁面的content="text/html; charset=utf-8"還是content="text/html; charset=GBK"IE8發送的都是www.google.com.hk/?test=%D6%D0,可以看出這是GBK的URI編碼,IE8對超連結中的URI編碼與頁面的編碼無關,與系統的預設編碼有關;而在windosx xp中,IE8發送的是頁面編碼採用的字元集進行的URI編碼,如果頁面編碼為GBK,IE6發送的為GBK的頁面編碼,如果頁面編碼為UTF-8則只發送UTF-8的URI編碼的前兩個位元組;在其他的瀏覽器中,如Firefox和chrome,其URI編碼採用的是頁面的編碼。
|
作業系統 |
瀏覽器 |
頁面編碼 |
發送的請求字串 |
說明 |
|
windows7 |
IE8中文 |
UTF-8 |
test=%D6%D0 |
windows7中,採用GBK字元集進行URI編碼與頁面的編碼無關 |
|
windows7 |
IE8中文 |
GBK |
test=%D6%D0 |
|
XP |
IE8中文 |
UTF-8 |
test=%E4%B8%AD |
xp中與採用頁面編碼的字元集進行URI編碼 |
|
XP |
IE8中文 |
GBK |
test=%D6%D0 |
|
windows2003 |
IE6中文 |
GBK |
test=%D6%D0 |
GBK是正確,UTF-8是不正確 |
|
windows2003 |
IE6中文 |
UTF-8 |
test=%E4%B8 |
|
-- |
chrome中文,firefox英文 |
UTF-8 |
test=%E4%B8%AD |
採用頁面編碼的字元集進行URI編碼 |
|
-- |
chrome中文,firefox英文 |
GBK |
test=%D6%D0 |
可以看出,直接在URL中帶中文,IE的不同版本在不同的作業系統中進行URI編碼的結果可能不一樣,chrome和firefox使用編碼和表單的get方式的編碼一致,因此,直接在連結中寫非asc字元是很危險的,因為字元的編碼方式和用戶端的環境有關。所以為了避免瀏覽器進行不確定的URI編碼,需要在程式中將中文進行URI編碼後在放到URL中,JavaScript提供了encodeURI()函數,它提供的是UTF-8的URI編碼,也可以通過java.net.URLEncoder.encode(str,"字元集")進行編碼
3)ajax
ajax可以指定get方式或者post方式,情況和上面說的類似 2. 應用伺服器擷取參數
在servlet中一般通過request.getParameter()來獲得瀏覽器發送過來的參數,需要注意的是,伺服器Servlet的最底層接受到的是InputStream,也就是位元組流,request.getParameter()返回的是一個字串,因此在getParameter()方法的內部存在解碼的過程,而解碼所採用的字元集根據應用伺服器和作業系統的不同有可能不同,ServletRequest介面提供了一個方法:setCharacterEncoding()來設定getParameter解碼的字元集,這個方法必需在getParameter之前調用,通過查看tomcate的原始碼發現getParameter在第一次調用時會去初始化一個map的對象,map中儲存的就是參數名和參數值,這些值就是根據設定的字元集進行解碼的,一旦這些對象解碼完畢,下次調用就直接從map中取值,而不需要重新去解碼了,所以setCharacterEncoding必需在getParameter之前調用才有作用,也有人說這個方法只對post傳遞參數有效,而對get方法傳遞的參數無效,對tomcat5確實是這樣的,但是對Websphere和apsuic,setCharacterEncoding對post和get同樣有效。
|
應用伺服器 |
伺服器所在系統預設編碼 |
頁面編碼 |
提交方式 |
URI編碼 |
setCharacterEncoding |
getParameter結果 |
備忘 |
|
websphere6.1 |
GBK |
UTF-8 |
POST |
- |
UTF-8 |
正確 |
伺服器預設配置 |
|
POST |
- |
GBK |
錯誤 |
|
GET |
UTF-8 |
UTF-8 |
正確 |
|
超連結 |
GBK |
GBK |
正確 |
|
tomcat5.5 |
GBK |
UTF-8 |
POST |
|
UTF-8 |
正確 |
未設定URIEncoding和 useBodyEncodingForURI |
|
POST |
|
GBK |
錯誤 |
|
GET |
UTF-8 |
UTF-8 |
錯誤 |
|
超連結 |
GBK |
GBK |
錯誤 |
|
apusic5.1 |
GBK |
UTF-8 |
POST |
|
UTF-8 |
正確 |
伺服器預設配置 |
|
POST |
|
GBK |
錯誤 |
|
GET |
UTF-8 |
UTF-8 |
正確 |
|
超連結 |
GBK |
GBK |
正確 |
從上面的表格可以看出,Websphere6.1,apusic5.1應用伺服器的get和post方法其getParameter解碼所用的字元集是setCharacterEncoding所指定的字元集,tomcat5的post方法使用的是setCharacterEncoding,但是get方法卻不是。在回過頭看看這幾個實驗的過程,瀏覽器使用post方法時將會採用頁面的字元集進行編碼成位元組流發送到伺服器,伺服器接收到位元組流後根據setCharacterEncoding設定的字元集進行解碼,獲得字串,
也就是說,如果使用post方法提交,只要保證“頁面的編碼的字元集=setCharacterEncoding設定的字元集”那麼getParameter獲得的值就是正確的,get和超連結的方式類似,
表單使用get提交時,會根據頁面的編碼進行encodeURI,超連結的方式程式可以根據指定的字元集進行URI編碼,
兩種方式的共同點是瀏覽器都會進行URI編碼,在Websphere中,get和超連結的方式只要“URI編碼的字元集=setCharacterEncoding的字元集”,那麼getParameter的結果就是正確的,而使用get提交表單時其“URI編碼的字元集=頁面編碼的字元集”,超連結的URI編碼的字元集在上面說過,chrome和Firefox瀏覽器中URI編碼的字元集=頁面編碼的字元集,但是IE卻不是,沒有規律。
tomcat5.5中getParameter擷取get方法或超連結傳過來的參數時預設會用ISO8859-1進行解碼,例如瀏覽器發送UTF-8的編碼的請求,tomcat5.5的getParameter使用ISO8859-1解碼,這時的結果是錯的,如果要獲得正確的值,需要在tomcat5.5的getParameter的時候採用UTF-8進行解碼,通過設定URIEncoding="UTF-8"或者useBodyEncodingForURI="true",就能讓tomcat在的getParameter時採用UTF-8解碼(useBodyEncodingForURI="true"表示解碼的字元集採用與頁面編碼相同的字元集),如果不通過配置要並且需要獲得正確的值,則需要程式進行轉碼,因為getParameter是通過ISO8859-1解碼的,所有先通過getParameter().getBytes("ISO8859-1")編碼成原來的位元組數組,然後使用UTF-8字元集解碼為字串:new String(getParameter().getBytes("ISO8859-1"),"UTF-8") 3. 設定瀏覽器的頁面編碼
伺服器向瀏覽器發送的也是經過編碼成位元組流在網路上傳輸,瀏覽器接收到位元組流之後使用指定的字元集解碼成字串再進行展現,如果這兩個環節的字元集不一致也會導致亂碼的問題,
例如靜態HTML檔案或jsp中都是以UTF-8儲存的,則需要告訴瀏覽器用UTF-8來進行解碼,
如果是jsp可以通過<%@ page contentType="text/html; charset=UTF-8" language="java" %>來進行設定, 靜態檔案可以通過 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 進行設定, 如果在servlet中直接進行輸出,可以通過response.setCharacterEncoding("UTF-8"),setContentType("text/html;charset=UTF-8"),setHeader("Content-Type","text/html;charset=UTF-8")進行設定,
這些操作都相當於在response的頭部增加"Content-Type:text/html;charset=UTF-8"資訊,
header中的編碼資訊的優先順序要高於html的meta標籤,也就是說如果serlvet中設定了setContentType("text/html;charset=UTF-8"),jsp設定了<meta http-equiv="Content-Type" content="text/html; charset=GBK"/>則瀏覽器會按照UTF-8字元集進行解碼,