天天看點

JavaWeb的編碼問題

Javaweb的編碼

           于海強

javaweb中涉及的編碼問題比較多,慢慢總結一下(這裡面有很多截圖顯示不出來,可以看我給的附件,是一個word文檔)

http://pan.baidu.com/s/1pLSuop9

0.為什麼需要編碼,解碼,

       無論是圖檔,文檔,聲音,在網絡IO,磁盤io中都是以位元組流的方式存在及傳遞的,但是我們拿到位元組流怎麼解析呢?這句話就涉及了編碼,解碼兩個過程,從字元資料轉化為位元組資料就是編碼,從位元組資料轉化為字元資料是解碼,可能有人疑問,一個字元不是一個位元組,兩個位元組嗎?一堆字元不就是一堆位元組嗎,需要轉什麼?好,剛才所說 的以及涉及到編碼了,有的編碼是一個位元組一個字元,就像ASCII碼,但是漢字以及其他語言文字太多,很明顯一個位元組不能表示所有字元,是以才會引申出如 此多的編碼,現在主要讨論ISO-8859-1,utf-8,gbk ,其中iso-8859編碼是無法對中文進行編碼的

        String ISO = "ISO-8859-1";

        String UTF = "UTF-8";

        String GBK = "GBK";

        String string = "很開心分享經驗";

        byte[] bytes = string.getBytes(ISO);

        System.out.println("結果:"+new String(bytes,ISO));

        for(byte b:bytes){

            System.out.print(b+" ");

        }

ISO隻要遇到不認識的字元,都會将其用63表示,顯示出來,也就是 ?  所有的都變成了問号 這裡可以看到六個中文對應6個 ?

順便說一下,英文不會涉及iso,utf-8,gbk,編碼問題,因為英文可以用ASCII碼表示,這幾種編碼對ASCII碼相容

String string = "很開心分享經驗  happy the world";

32代表 空格

utf-8編解碼

String ISO = "ISO-8859-1";

        String UTF = "UTF-8";

        String GBK = "GBK";

        String string = "很開心分享經驗";

        byte[] bytes = string.getBytes(UTF);

        System.out.println("結果:"+new String(bytes,UTF));

        for(byte b:bytes){

            System.out.print(b+" ");

        }

可以看到 編碼沒有問題,而且每個漢字占用三個位元組 ,一共21個(UTF-16一共占用16個)

GBK編碼

代碼省略了

編解碼還是沒有問題, 但是每個漢字占用 兩個位元組,一共14個

我們知道了,隻要遇到一大堆??????這樣的亂碼,一般可以确認,有一個地方用到了iso的編碼,這是中文不能使用的編碼除此之外,我們還會看到很多種其他的亂碼例如:

1.   寰堝紑蹇冨垎浜粡楠�

2. 2.   �ܿ��ķ��?�� 

3.  ºÜ¿ªÐÄ·ÖÏí¾Ñé  

4. 很开心分享经骠

這四種亂碼,好像都不一樣哦,各有各的風采,然而并看不懂。

1.   寰堝紑蹇冨垎浜粡楠�    UTF-GBK

2.   �ܿ��ķ��?��               GBK-UTF

3.  ºÜ¿ªÐÄ·ÖÏí¾Ñé                     GBK-ISO

4. 很开心分享经骠                 UTF-ISO

(5 裥벀菥袆ꯧ뮏�    utf-8 ---utf-16

這四種分别對應,不同的編碼-解碼  例如第一個  很開心分享經驗  用utf-8編碼後,gbk解碼後 就變成了這坨  寰堝紑蹇冨垎浜粡楠�  ,剩下三坨不多說

現在總結下(一次編解碼):

1. 隻要是iso對中文字元編碼就一定是一大堆?????,為什麼呢,因為我們說了iso把不認識的都轉成63     63是什麼在ASCII碼中是? 而這些編碼基本都相容ASCII,是以

隻要是一大坨???????就一定有一個地方采用的是iso編碼,而後面還會說道iso編碼是很多地方的預設編碼(預設的為什麼不是utf-8,,郁悶!!!)

2  當我們看到亂碼之後,不要慌,第一步先不要想在哪裡出現編碼問題,先考慮  可能是哪一種編解碼 錯誤。而這種錯誤是有章可循的。上邊内四個基本差不多,(如果還有發現别的,我會再加上)

那麼如果出現其他好多次編解碼呢?  那這個問題就複雜的多了

1  .String ISO = "ISO-8859-1";

        String UTF = "UTF-8";

        String GBK = "GBK";

        String string = "很開心分享經驗";

        byte[] bytes = string.getBytes(UTF);

        String string2 = new String(bytes,ISO);

        byte[] bytes2 = string2.getBytes(ISO);

        String string3 = new String(bytes2,UTF);

        System.out.println("結果:"+string3);

UTF編碼,iso解碼,ios解碼,utf-8解碼  中間經曆了曲折,但是,最終由變成了 中文,好艱難

但是我們應該為此慶幸嗎?  我覺得不能,你最好也這麼覺得,所有的編解碼,要統一

統一之前,我們也應該明白,我們總會有疏漏的地方,萬一一不留神呢,,是以再看看其他的混合編解碼

2.String string = "很開心分享經驗";

        byte[] bytes = string.getBytes(UTF);

        String string2 = new String(bytes,ISO);

        byte[] bytes2 = string2.getBytes(UTF);

        String string3 = new String(bytes2,ISO);

        System.out.println("結果:"+string3);

這一次我們 進行了兩次 utf編碼,ios解碼, 但是結果什麼樣子呢

翻翻前面的,基本差不多,隻不過小寫變大寫,最重要的是長度增加了一倍

3.

     byte[] bytes = string.getBytes(UTF);

        String string2 = new String(bytes,GBK);

        byte[] bytes2 = string2.getBytes(UTF);

        String string3 = new String(bytes2,GBK);

        System.out.println("結果:"+bytes2.length+string3);

這一次我們進行了 一個utf-8,編碼,gbk解碼,utf-8編碼,gbk解碼,依然是亂碼,長度又增加了 二分之一(utf-8表示一個中文三個位元組,gbk是兩個位元組)

4.byte[] bytes = string.getBytes(UTF);

        String string2 = new String(bytes,GBK);

        byte[] bytes2 = string2.getBytes(GBK);

        String string3 = new String(bytes2,UTF);

        System.out.println("結果:"+string3);

這次是utf-8編碼,gbk解碼,gbk編碼,utf-8解碼。最終的結果好玩

一部分中文被正确顯示,另外一些漢字慘遭抛棄。。。

5.byte[] bytes = string.getBytes(GBK);

        String string2 = new String(bytes,UTF);

        byte[] bytes2 = string2.getBytes(UTF);

        String string3 = new String(bytes2,GBK);

        System.out.println("結果:"+string3);

這一次是GBK編碼,utf-8解碼,utf-8編碼,gbk解碼

6、

byte[] bytes = string.getBytes(GBK);

        String string2 = new String(bytes,UTF);

        byte[] bytes2 = string2.getBytes(GBK);

        String string3 = new String(bytes2,GBK);

        System.out.println("結果:"+string3);

這次我們gbk編碼,utf-8解碼,gbk編碼,gbk解碼,

得到的結果感人::

一大坨 ??? 剛才我說的都是一大坨 ???編碼肯定是iso,但是得有前提,長度相同

到這裡,不能在總結了,意思很明确,經過一次兩次編碼,結果可能已經面目全非,但是仔細分析,每一種情況的具體結果還是不同的,對于我們很多人來說,要想記住這些所有的亂碼情況,是不可能的,但是如果我們真的遇到了一些多次編解碼,多種編碼方式,出現的亂碼時,有過這方面的試驗經驗,或許可以解決的更快一些。

下面從請求處理的流程,以及具體流程的某個階段可能出現的亂碼問題

1.http請求的編碼

送出表單(一般設定為method = POST ,一下說的表單預設是post),或者在位址欄直接輸入url位址(get請求)

首先先說get方式,也就是輸入位址欄 的url

1.http://localhost:8080/TestCharSet/test?charset=中文

紅色部分為pathinfo,也就是路徑部分,綠色部分是QueryInfo部分,也就是查詢字元串,在背景我們一般這樣擷取

String charset = request.getParameter(“charset”);

以上我在位址欄中輸入的,當我從 位址欄複制到word中時,他轉成了這個

http://localhost:8080/TestCharSet/test?charset=%E4%B8%AD%E6%96%87

 後面的%加上數字字母都是URL編碼 加上%是因為16進制表示,是以在前面加個%

URL編碼的過程很簡單,如下:

1. 将待編碼字元原先的存儲編碼看成一個16進制流【将原2進制流按 位元組拆分,每個位元組都用2位16進制數表示】;

2. 在每兩位16進制數(即一個完整的位元組)前加一個%,得到最終編碼結果;

        對于漢字來說,首先要看其本身存儲時所使用的編碼是UTF-8還是GB2312。同樣的漢字,存儲編碼不同,經URL編碼後的結果自然也不同。例如“川”,使用UTF-8編碼存儲時為 e5b79d ,經URL編碼後則為 %e5%b7%9d ;使用GB2312編碼存儲時為 b4a8 ,經URL編碼後則為 %b4%a8 。

        解碼的時候也很簡單,将編碼裡的%号去掉,得到一個16進制流,這個16進制流轉回2進制流,得到的就是原字元的存儲編碼。剩下的一個重要問題是怎麼了解這個還原出來的存儲編碼(即原字元使用的存儲編碼方式)?分三種情況:

· 對于HTTP請求正文中的URL編碼,我們可以檢視請求頭部中 Charset 頭域的值,它指定了請求封包所使用的字元集(即存儲編碼方式)。如圖1,因為 Charset 的值為UTF-8,是以我們對解碼後的結果就應當按UTF-8編碼了解了;

· 因為使用UTF-8編碼時,一個漢字的本身存儲占三個位元組;而使用GB2312編碼,一個漢字的本身存儲占兩個位元組。是以如果我們能确定被編碼的是純漢字流的話,我們可以根據解碼後的結果占用的位元組數是3或者2的倍數來大緻推斷其存儲編碼方式; 

· 上述方法都不行的話,就隻能在譯碼的時候都試一下了

 具體的url編碼請檢視連結 http://www.tuicool.com/articles/3mUNFz 這裡寫的很好

前面說到了 浏覽器把一個具體編碼的字元序列按照url編碼 為16進制,同樣伺服器端,按照url解碼,将url進行解碼 得到了具體的字元序列,那麼伺服器拿到了這個字元序列怎麼辦呢,就可以讀取了嗎,不能,我們還需要知道這個字元序列是什麼編碼方式,否則我們根本正常讀取不了(否則就會亂碼)

2)post方式的編碼

 例如表單POST請求,會将送出的參數放到請求主體部分

例如name就被放在主體部分,同樣,它也是浏覽器通過url編碼(16進制編碼)把資料傳送到伺服器端,具體的伺服器拿到這個16進制解碼的結果,也就是這個字元序列,如何處理,是按照什麼方式解析呢,同樣是 制定的charset值,但是一般表單送出後,伺服器端需要我們指定具體的charset進行解碼,具體的方式

大家都用過:request.setCharacterEncoding(charset);

3、應用伺服器如何解析參數

   我們知道url也就是get請求的路徑部分,需要解碼,解碼的方式是charset指定的方式,但是具體的,可能頭檔案中并沒有指定charset,這時 應用伺服器會采用預設的編碼方式

具體到tomcat中

 <Connector URIEncoding="UTF-8" port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

               redirectPort="8443" />

這個uriencoding屬性可以設定tomcat的解析的uri的編碼

如果不設定,預設是IOS-8859-1,uri中存在中文字元,即使設定了request.setCharaeterEncoding(“utf-8”)在request中也解析不出來的。

如下

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

System.out.println("GET請求處理");

req.setCharacterEncoding("utf-8");//在這裡設定了utf-8

String string = req.getParameter("charset");

if(string!=null){

System.out.println(string);

}else {

System.out.println("沒有擷取到變量");

}

PrintWriter pw = resp.getWriter();

pw.println(req.getRequestURI()+"請求成功");

pw.close();

}

請求路徑中包含中文

http://localhost:8080/TestCharSet/test?charset=中文

如果設定了URIEncoding="UTF-8"之後,即使沒有設定reqest.setCharaeterEncoding,也可以擷取到參數,但是對于post請求,無法擷取到name

當設定了reqest.setCharaeterEncoding(utf-8)後,post請求可以擷取到

我們可以總結一下,也就是說url中的編碼必須統一uriEncoding設定,可以不設定reqest.setCharaeterEncoding(utf-8)

但是如果遇到post請求,參數資訊在請求主體中,那我們必須設定

reqest.setCharaeterEncoding(utf-8)這樣才能保證擷取到正确的參數資訊。

這裡多提一點,tomcat在對請求頭檔案解析的時候,預設是先不解析請求主體字元串,因為字元串操作非常耗費性能,tomcat把解析工作延遲到第一次調用 req.getParameter中進行,之後便将所有的參數全部注入到tomcat中的一個ParameterMap資料結構中,在解析之後,你即使通過req.setCharaeter也沒有用了。

(在由byte[]流轉化為Java中的String時,需要指定編碼,這個編碼就是通過request設定的)

Tomcat還有一個參數useBodyEncodingForURI,這個參數是什麼意思呢,當它為false時,對url中的編碼采用tomcat預設的或者uriEncoding設定的編碼方式。當為true時,get請求中的查詢字元串按照http請求主體中的編碼方式解碼,而請求主體的解碼方式,我們通過

request.setCharaeterEncoding設定,而浏覽器發送請求主體的編碼方式是發送post,get請求的本界面的charset進行編碼。後面會具體談到浏覽器發送http請求的編碼

不光request,response也需要字元轉化,看個例子

這時處理get請求的servlet代碼,像螢幕列印輸出請求連結,以及“請求成功”

PrintWriter pw = resp.getWriter();

pw.println(req.getRequestURI()+"請求成功");

pw.close();

可以看到請求成功是亂碼,因為response将”請求成功“轉化為 byte[]流時,預設采用的

ISO-8859-1,是以列印的都是????

基于此reponse也要設定字元編碼,response.setCharaeterEncoding(“utf-8”)

這時顯示正常了

但是可能有的小夥伴顯示的結果可能是這個

還是亂碼,往上面找我們總結的四種情況,最接近第一種,也就是說用utf-8編碼,gbk解碼的情況,事實也是如此,浏覽器接收到byte[]流後,按照的是GBK解碼,我用的是火狐浏覽器,在文字編碼設定中,将簡體中文改成Unicode後,即正常顯示。

或者我們采用一種更加優雅的方式

pw.println("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">");

列印meta标簽,這樣即使我們手動指定文字編碼為gbk(簡體中文),浏覽器接收到這個标簽後,會預設按照這個标簽中指定的編碼,進行解碼顯示。同樣,meta标簽指定的編碼也要和response.setXXX方法設定的一樣,否則也會亂碼

再繼續深入一下,這是通過response列印輸出,如果直接跳轉到jsp頁面呢?首先我們先不設定 response的編碼集也就是預設的轉到Charser.jsp界面該界面如下

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head>

    <base href="<%=basePath%>" target="_blank" rel="external nofollow" >

    <title></title>

    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">    

<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

<meta http-equiv="description" content="This is my page">

<!--

<link rel="stylesheet" type="text/css" href="styles.css" target="_blank" rel="external nofollow" >

-->

  </head>

  <body>

    <h1>copyright @ 青銅器工作室  </h1>

  </body>

</html>

在這個界面裡,有一段中文,看看這個界面能不能正常顯示

req.getRequestDispatcher("/Charset.jsp").forward(req, resp);

這個将請求轉發到Charset.jsp這個界面

結果如圖,response在沒有設定編碼情況下,還是将jsp中的中文按照iso進行編碼,變成了???

有什麼方式可以避免這種情況發生呢

事實上我們可以看到

即使加上<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

如果不用response設定預設編碼,同樣會亂碼 因為response設定的編碼是用來編碼的,就是講字元轉化為位元組數組,而meta設定的utf-8是用來解碼的,也就是說告訴,浏覽器,你應該如何顯示

但是我發現,

當設定了response的編碼

設定了<meta http-equiv="Content-Type" content="text/html;charset=gbk" />

時用戶端沒有顯示亂碼,預測結果是用戶端顯示界面時應該按gbk解碼 用utf-8編碼後的字元。

關鍵在于 charset.jsp這個界面我是通過request轉發過去的,在進行中response設定的utf-8已經指定了http響應頭的編碼

req.getRequestDispatcher("/Charset.jsp").forward(req, resp);

事實上response設定的utf-8已經設定了jsp編碼的方式,同時在響應頭中指定了charset,這樣jsp設定的charset,沒有作用。

那麼jsp中的

<meta http-equiv="Content-Type" content="text/html;charset=gbk" />

以及在

<%@ page language="java" contentType="text/html;charset=GBK"  import="java.util.*" pageEncoding="UTF-8"%>中設定的ContentType在哪裡其作用呢?pageEncoding又是什麼呢?

以下引用

http://www.cnblogs.com/loulijun/archive/2012/03/28/2421568.html下内容

pageEncoding是jsp檔案本身的編碼

  contentType的charset是指伺服器發送給用戶端時的内容編碼

  JSP要經過兩次的“編碼”,第一階段會用pageEncoding,第二階段會用utf-8至utf-8,第三階段就是由Tomcat出來的網頁, 用的是contentType。

  第一階段是jsp編譯成.java,它會根據pageEncoding的設定讀取jsp,結果是由指定的編碼方案翻譯成統一的UTF-8 JAVA源碼(即.java),如果pageEncoding設定錯了,或沒有設定,出來的就是中文亂碼。

  第二階段是由JAVAC的JAVA源碼至java byteCode的編譯,不論JSP編寫時候用的是什麼編碼方案,經過這個階段的結果全部是UTF-8的encoding的java源碼。

  JAVAC用UTF-8的encoding讀取java源碼,編譯成UTF-8 encoding的二進制碼(即.class),這是JVM對常數字串在二進制碼(java encoding)内表達的規範。

  第三階段是Tomcat(或其的application container)載入和執行階段二的來的JAVA二進制碼,輸出的結果,也就是在用戶端見到的,這時隐藏在階段一和階段二的參數contentType就發揮了功效

  contentType的設定.

  pageEncoding 和contentType的預設都是 ISO8859-1. 而随便設定了其中一個, 另一個就跟着一樣了(TOMCAT4.1.27是如此). 但這不是絕對的, 這要看各自JSPC的處理方式. 而pageEncoding不等于contentType, 更有利亞洲區的文字 CJKV系JSP網頁的開發和展示, (例pageEncoding=GB2312 不等于 contentType=utf-8)。

  jsp檔案不像.java,.java在被編譯器讀入的時候預設采用的是作業系統所設定的locale所對應的編碼,比如中國大陸就是GBK, 台灣就是BIG5或者MS950。而一般我們不管是在記事本還是在ue中寫代碼,如果沒有經過特别轉碼的話,寫出來的都是本地編碼格式的内容。是以編譯器 采用的方法剛好可以讓虛拟機得到正确的資料。

  但是jsp檔案不是這樣,它沒有這個預設轉碼過程,但是指定了pageEncoding就可以實作正确轉碼了。

1、pageEncoding="UTF-8"的作用是設定JSP編譯成Servlet時使用的編碼。 

     衆所周知,JSP在服務 器上是要先被編譯成Servlet的。pageEncoding="UTF-8"的作用就是告訴JSP編譯器在将JSP檔案編譯成Servlet時使用的 編碼。通常,在JSP内部定義的字元串(直接在JSP中定義,而不是從浏覽器送出的資料)出現亂碼時,很多都是由于該參數設定錯誤引起的。例如,你的 JSP檔案是以GBK為編碼儲存的,而在JSP中卻指定pageEncoding="UTF-8",就會引起JSP内部定義的字元串為亂碼。 

     另外,該參數還有一個功能,就是在JSP中不指定contentType參數,也不使用response.setCharacterEncoding方法時,指定對伺服器響應進行重新編碼的編碼。

2、contentType="text/html;charset=UTF-8"的作用是指定對伺服器響應進行重新編碼的編碼。 

    在不使用response.setCharacterEncoding方法時,用該參數指定對伺服器響應進行重新編碼的編碼。

剛才我們就是在設定response.setCharacterEncoding(utf-8)後,設定contentType為gbk,但是用戶端依然按照response的設定,是以response對contentType的優先級高

3、request.setCharacterEncoding("UTF-8")的作用是設定對用戶端請求進行重新編碼的編碼。

      該方法用來指定對浏覽器發送來的資料進行重新編碼(或者稱為解碼)時,使用的編碼。

4、response.setCharacterEncoding("UTF-8")的作用是指定對伺服器響應進行重新編碼的編碼。 

     伺服器在将資料發送到浏覽器前,對資料進行重新編碼時,使用的就是該編碼。

當我們直接通過通路jsp的方式,轉到jsp界面時,發現 在page标簽中contentType起作用了,我們設定的是gbk

然後我們發現通過meta設定的編碼為utf-8時,也并沒有出現亂碼,可見,浏覽器顯示時,并沒有按照meta标簽中指定的編碼,而是優先使用page标簽中的contentType中設定的值。這時即使設定了字元過濾器,字元過濾器中response中的設定也不會啟用,也即是說,

當直接通路jsp時,隻會看

<%@ page language="java"  contentType="text/html;charset=gbk" import="java.util.*" pageEncoding="UTF-8"%>

的設定,(過濾器什麼的都不會起作用)

當采用servlet中請求轉發方式時,過濾器或servlet中的response的設定會起作用

當在html頁面中,使用meta标簽會起作用,當沒有meta标簽中會預設按照utf-8編碼(并不推薦預設)

下面我們在分析一下,已開始讨論的浏覽器端的處理

預設的情況下,如果jsp中出現這個超級連結應該如何處理呢。

<a href="http://localhost:8080/TestCharSet/test?charset=中文" target="_blank" rel="external nofollow" >點選這裡</a>

浏覽器解析這個本界面時按照response的設定,或者page标簽中的contentType設定(也就是響應頭中的charset)進行解析,是以這個連結中的中文自然是按照響應中的設定進行編碼,而這是一個get請求,我們知道get請求浏覽器需要将url進行url編碼(16進制編碼),我們需要知道url本身的編碼是utf-8,還是gbk,這樣伺服器擷取到字元序列才能進行重新編碼,

當我們的url是通過超級連結跳轉的,編碼方式按照本界面response的字元編碼設定。

當我們直接通過位址欄輸入中文url,這時url會按照浏覽器自己預設的編碼方式編碼,火狐是utf-8,360也是utf-8,至于其他的浏覽器是不是utf-8不确定。

那麼post方式,浏覽器是采用什麼編碼的呢?

和上面所說的一樣,浏覽器會解析response設定的響應頭的charset,然後确定post請求的主體采用什麼編碼

如果響應頭中charset是utf-8時,那麼下次它發送http請求,主體部分會是utf-8.

引用這個連結中的一段話http://www.cnblogs.com/loulijun/archive/2012/03/28/2421568.html

  response.setCharacterEncoding("UTF- 8")的作用是指定對伺服器響應進行重新編碼的編碼。同時,浏覽器也是根據這個參數來對其接收到的資料進行重新編碼(或者稱為解碼)。是以在無論你在 JSP中設定response.setCharacterEncoding("UTF-8")或者 response.setCharacterEncoding("GBK"),浏覽器均能正确顯示中文(前提是你發送到浏覽器的資料編碼是正确的,比如正 确設定了pageEncoding參數等)。讀者可以做個實驗,在JSP中設定 response.setCharacterEncoding("UTF- 8"),在IE中顯示該頁面時,在IE的菜單中選擇"檢視(V)"à"編碼(D)"中可以檢視到是" Unicode(UTF-8)",而在在JSP中設定response.setCharacterEncoding("GBK"),在IE中顯示該頁面 時,在IE的菜單中選擇"檢視(V)"à"編碼(D)"中可以檢視到是"簡體中文(GB2312)"。 

     浏覽器在發送資料時,對URL和參數會 進行URL編碼,對參數中的中文,浏覽器也是使response.setCharacterEncoding參數來進行URL編碼的。以百度和 GOOGLE為例,如果你在百度中搜尋"漢字",百度會将其編碼為"%BA%BA%D7%D6"。而在GOOGLE中搜尋"漢字",GOOGLE會将其編 碼為"%E6%B1%89%E5%AD%97",這是因為百度的response.setCharacterEncoding參數為GBK,而 GOOGLE的的response.setCharacterEncoding參數為UTF-8。 

      浏覽器在接收伺服器資料和發送資料到伺服器 時所使用的編碼是相同的,預設情況下均為JSP頁面的response.setCharacterEncoding參數(或者contentType和 pageEncoding參 數),我們稱其為浏覽器編碼。當然,在IE中可以修改浏覽器編碼(在IE的菜單中選擇"檢視(V)"à"編碼(D)"中修 改),但通常情況下,修改該參數會使原本正确的頁面中出現亂碼。一個有趣的例子是,在IE中浏覽GOOGLE的首頁時,将浏覽器編碼修改為"簡體中文 (GB2312)",此時,頁面上的中文會變成亂碼,不理它,在文本框中輸入"漢字",送出,GOOGLE會将其編碼為"%BA%BA%D7%D6",可 見,浏覽器在對中文進行URL編碼時,使用的就是浏覽器編碼。

是以至此整個浏覽器發送http請求,伺服器擷取資料,發送資料,浏覽器接受資料中涉及的編碼基本上都已經說了,那項目中還有哪些編碼問題呢,或者哪些編碼問題隐藏比較深呢?

1.資料庫的編碼,資料庫一般不會出現編碼問題前提是我們建立資料庫時需要指定編碼

CREATE TABLE `t_image` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `fkey` varchar(50) DEFAULT NULL,

  `src` varchar(500) NOT NULL,

  `des1` varchar(500) DEFAULT NULL,

  `des2` varchar(500) DEFAULT NULL,

  `des3` varchar(500) DEFAULT NULL,

  `des4` varchar(500) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

同時連接配接資料庫時也要制定編碼

jdbc:mysql://localhost:3306/psych?Unicode=true&characterEncoding=utf-8

這種情況下基本上不會出現資料庫編碼問題,在這種情況下,如果出現亂碼問題,最後考慮資料庫,因為有可能存進去的時候就是亂碼

什麼意思呢,例如你将一個 string轉化為byte[]使用gbk編碼,然後存進資料庫,這時你如果讀取後,将byte[]按照utf-8進行解碼,本身就是錯的,後面我會說到我的一個經曆,這個經曆很久了,但bug隐藏的很深,以至于 寫部落格之前,我才恍然大悟為什麼出現問題。

2.tomcat的預設編碼問題。

為什麼要說tomcat的預設編碼?剛才不已經說了嘛,通過uriEncoding設定。此處說的預設編碼設定是tomcat本身作為一個javaweb應用伺服器,它本身就是一個程序,而一個程序執行個體就是一個虛拟機,我們在java裡很少說程序,取而代之的是虛拟機,實際上,tomcat也是從main函數開始,中間有線程監聽請求,監聽到交給處理器線程處理,處理器針對http請求流進行解析,将其封裝為request,response,其中response中封裝了outputStream,也是即将發送給用戶端的輸出流,然後映射器(tomcat5以後沒了,但道理一樣)找到我們定義的servlet,将其加載,包裝成tomcat中的類進行業務處理。以上我們讨論的都是每層每個階段之間交換資料出現的編碼問題。

而這裡談到的預設編碼是指tomcat程序(虛拟機)本身采用的字元編碼,可以通過

System.out.println(java.nio.charset.Charset.defaultCharset());

檢視在windows下預設是gbk,linux下預設是utf-8

一般情況下,我們即使在windows下面,一個java程序采用的編碼是utf-8

但是tomcat程序不同,它是gbk。

那這個預設字元編碼作用是什麼呢?

String string = "很開心分享經驗";

byte[] bytes = string.getBytes(UTF);

String string2 = new String(bytes);

這段代碼執行結果是什麼呢那要看具體的環境了在windows下的tomcat,絕對是亂碼

在linux下的tomcat,就不亂,如果是預設的程序(你自己寫個main,然後這三代碼,不加任何虛拟機初始參數)那不是亂碼。

一開始我并沒有發現windows下的tomcat預設編碼是gbk,我在測試資料庫時,采用的是本地測試,把字元串轉化為byte[]時,采用的是預設的,這時預設的應該是utf,然後tomcat在顯示時依然采用預設的進行解碼(gbk)這時,就出現了亂碼。然而我當時百思不得其解,誤以為是資料庫的錯。浪費了不少時間依然沒有解決。還在懷疑是不是web請求進行中出現亂碼。這就是知識面不全,存在疏漏之處的後果。有時候我們很有必要建立全面深刻的知識體系,不能一知半解,即使是小知識點也要好好解決掉,否則出現了bug這些不足都會以時間成本來報複你。

那預設編碼如何解決呢,答案是這樣的,我找了很多初始參數但是都不對,于是想個邪招

Field field = Charset.class.getDeclaredField("defaultCharset");

field.setAccessible(true);

try {

field.set(Charset.class,Charset.forName("GBK"));

} catch (IllegalArgumentException e) {

//

e.printStackTrace();

} catch (IllegalAccessException e) {

//

e.printStackTrace();

}

通過反射擷取靜态類的私有字段,然後修改,這時

String string = "很開心分享經驗";

byte[] bytes = string.getBytes(UTF);

String string2 = new String(bytes);

我們通過getBytes,以及new String().預設的都是我們設定的gbk,或者utf-8了。

繼續閱讀