成就夢想

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            21 隨筆 :: 22 文章 :: 6 評論 :: 0 Trackbacks
          <2012年4月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿

          隨筆分類(4)

          隨筆檔案(8)

          文章分類(13)

          文章檔案(10)

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

              我們部門底層的web應用有一套處理編碼的流程機制,主要處理因歷史原因或者跨部門產品之間gbk和utf8剪不清理還亂的關系。
          前2天同事有詢問相關編碼的問題,在此做個整理,希望能夠對大家有所幫助。

          首先是編碼的歷史,這是一個很有意思的解讀 。寫的很幽默,便于理解。

          下面主要寫于與java想關的編碼,主要解讀unicode ,utf8 和gbk。

          JVM里面的任何字符串資源都是Unicode,就是說,任何String類型的數據都是Unicode編碼。沒有例外,因此我們可以這么說,JVM里面的String是不帶編碼的。因為他就有且只對應一種Unicode。
          一個字符的Unicode編碼是確定的。也就是說Unicode是一種字符集,里面字符與編碼是一一對應的,這里有個碼表可查,unicode 碼表。但是在實際傳輸過程中,由于不同系統平臺的設計不一定一致,以及出于節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF)。我們常用的就是UTF8.
          UTF8是如何存儲一個Unicode編碼的呢。也就是utf8作為一種Unicode Transformation Format是如何工作的呢?
          首先utf8 是可變長的,UTF-8使用一至四個字節為每個字符編碼。參照下表,我們把精力放在第1列,第3列,和注釋。
          對于ASCII字符,可以用七個bit位來表示,x6 x5 x4 x3 x2 x1 x0.第八個bit永遠是0。
          第128到2047個字節,要用10個bit來表示,110yyyyy(C0-DF) 10zzzzzz(80-BF)
          第2048到65535個字節,要用16個bit來表示,Utf-8把這些字節編成下面這樣的三個byte。1110xxxx(E0-EF) 10yyyyyy 10zzzzzz
          大于65535其余用4個byte來表示。

          舉個例子:“中國”的中,unicode編碼是“\u4e2d", 對應的編碼除了查表,java可以用命令行,運行 native2ascii 進行轉化。
          用window 自帶的附件中的計算器(查看->科學型),轉化成10進制為20013,二進制是100111000101101 
          通過上面的表,可知,轉化成utf8后為三個字節。
          只需要將剛才轉化的二進制(上面標紅的)將下面的xxxx,yyyyyy,zzzzzz補齊即可。
          1110xxxx(E0-EF) 10yyyyyy 10zzzzzz,我們從低位開始補起,不夠的用0補齊。
          11100100 10111000 10101101  ,換成16進制為E4 B8 AD。
          好了我們用java代碼來驗證下,是否正確。
          public static void main(String[] args) {
                  String ha 
          = "";
                  
          byte b[] = null;
                  
          try {
                      b 
          = ha.getBytes("utf-8");
                  } 
          catch (Exception e) {
                      System.exit(
          -1);
                  }

                  
          for (int i = 0; i < b.length; i++) {
                      System.out.print(Integer.toHexString(b[i]).substring(
          6+ " ");
                  }

              }
          輸出果然是:e4  b8 ad。
          utf8 wiki中有下描述:
          • 對于UTF-8編碼中的任意字節B,如果B的第一位為0,則B為ASCII碼,并且B獨立的表示一個字符;
          • 如果B的第一位為1,第二位為0,則B為一個非ASCII字符(該字符由多個字節表示)中的一個字節,并且不為字符的第一個字節編碼;
          • 如果B的前兩位為1,第三位為0,則B為一個非ASCII字符(該字符由多個字節表示)中的第一個字節,并且該字符由兩個字節表示;
          • 如果B的前三位為1,第四位為0,則B為一個非ASCII字符(該字符由多個字節表示)中的第一個字節,并且該字符由三個字節表示;
          • 如果B的前四位為1,第五位為0,則B為一個非ASCII字符(該字符由多個字節表示)中的第一個字節,并且該字符由四個字節表示;

          因此,對UTF-8編碼中的任意字節,根據第一位,可判斷是否為ASCII字符;根據前二位,可判斷該字節是否為一個字符編碼的第一個字節; 根據前四位(如果前兩位均為1),可確定該字節為字符編碼的第一個字節,并且可判斷對應的字符由幾個字節表示;根據前五位(如果前四位為1),可判斷編碼 是否有錯誤或數據傳輸過程中是否有錯誤。


          反過來,我們還是拿剛才的”中“為例,11100100 10111000 10101101 ,第一個字節開始為110,則讀第二個字節為10,第三個字節為10,則認為是utf8字符。
          于是就有了一個那個經典的“聯通"干不過”移動“的經典段子。
          我們在xp下,隨便建立一個文件,輸入"聯通",保存,這時你在打開是,發現”聯通"2個字符不見了。奇怪嗎??????
          我們知道默認保存的編碼是ANSI,實際也是類GBK的編碼。
          對應16進制為c1 aa cd a8, 轉化成二進制為11000001 10101010 11001101 10101000 ,我們來看,110xxxxx,10xxxxxx 正好符合utf8的形式。
          這時候文件編寫器以為你的文件是utf8的文件,然后默認已utf8的形式給你打開展示。于是就出現亂碼了。如果你在”聯通“后面隨便加幾個字符。就不出出現靈異事件了。

          那么我們繼續討論 GBK和Unicode是什么關系呢?
          實際上GBK我們可以看做是字符集,他也有自己一一對應的碼表。google一下,很容易查到。這里有個Unicode和GBk對應的表Unicode-GBk
          在java中,
          "我愛你莎莎".getBytes("gbk");
          進行轉化,其實就是類似查一個Unicode和GBk對應表進行轉化的。大家看一下Charset這個抽象類的那些子類就明白了。
          通過上面的描述GBk和UTF8關系也就很明朗了,完全可以通過Unicode進行中轉。

          同事在詢問編碼的問題時,一開始對類似如下代碼,相互轉變不太理解。
          byte b1[] = null;
                  b1 = "我愛你莎莎".getBytes("gbk");
                  System.out.println(new String(b1,"gbk"));
                  byte b2[] = null;
                  b2 = "我愛你莎莎".getBytes("utf8");
                  System.out.println(new String(b2,"utf8"));
                  System.out.println(new String (new String (b2,"gbk").getBytes("gbk"),"utf8"));
          其實我們可以把getBytes("gbk"),這個函數當做將unicode用gkb加密的過程,而new String(”xxx“,"編碼”)看成是解密的一個過程。

          大家思考一下最后面的那個輸出可以得到正確的結果嗎?為什么?

          下面我們來討論 ,通過http協議下的url傳輸后,編碼轉化問題。
          首先說明的是本人本地默認編碼是gbk。
          我們只用Servlet,不使用任何框架比如spring(因為使用框架時,框架也有一套自己自己的機制)如下代碼
          public class HttpEncode extends HttpServlet {
              @Override
              
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  String str 
          = req.getQueryString();
                  System.out.println(req.getCharacterEncoding());

                  String encode 
          = null;
                  
          try {
                      encode 
          = req.getParameter("encode");
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }

                  System.out.println(str);
                  System.out.println(encode);

              }

          }
          我們分別用jetty(版本6.1)和resin(版本3.1.8)下容器,測試如下請求     127.0.0.1/test?encode=%B9%FE  其中%B9%FE為GBk的編碼的漢字”哈“
          jetty容器下輸出為
          null
          encode=%B9%FE
          ?
          resin下為:
          null
          encode=%B9%FE
          null

          換做127.0.0.1/test?encode=%E5%93%88   ,utf8編碼的”哈“
          jetty和resin下都輸出如下
          null
          encode=%E5%93%88

          為什么會是這樣?
          我們拿jetty分析,在jetty的源碼中,
             public String getParameter(String name)
              {
                  
          if (!_paramsExtracted) 
                      extractParameters();
                  
          return (String) _parameters.getValue(name, 0);
              }

          對應的
          extractParameters(); 部分代碼
           if (_queryEncoding==null)
                          _uri.decodeQueryTo(_baseParameters);
                   
          然后
             public void decodeQueryTo(MultiMap parameters)
              {
                  if (_query==_fragment)
                      return;
                  _utf8b.reset();
                  UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b);
              }






          也就是如果_queryEncoding為null時,默認是用utf8進行解碼的。而resin也不例外。
          jetty中_queryEncoding的值可以通過org.mortbay.jetty.Request.queryEncoding 這個屬性給賦值而resin采用的是req.getCharacterEncoding()中的值為標準。
          要想在jetty下 127.0.0.1/test?encode=%B9%FE,獲取到正確的字符,代碼如下
              protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  String str 
          = req.getQueryString();
                  System.out.println(req.getCharacterEncoding());
                  req.setAttribute(
          "org.mortbay.jetty.Request.queryEncoding""gbk");
                  String encode 
          = null;
                  
          try {
                      encode 
          = req.getParameter("encode");
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }

                  System.out.println(str);
                  System.out.println(encode);

              }

          resin下只需要
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  String str 
          = req.getQueryString();
                  req.setCharacterEncoding(
          "gbk");
                  System.out.println(req.getCharacterEncoding());
                  String encode 
          = null;
                  
          try {
                      encode 
          = req.getParameter("encode");
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }

                  System.out.println(str);
                  System.out.println(encode);

              }

          通過上面想說明的是,不同的容器,默認編碼的策略是不一致的。只要我們了解編碼的基礎知識。通過一些封裝就很容易掌控這個局面。





          參考資料:
          Unicode wiki:   http://zh.wikipedia.org/wiki/Unicode
          jetty 源碼
          posted on 2012-04-18 11:38 iamct 閱讀(1713) 評論(0)  編輯  收藏 所屬分類: 基礎知識

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 永嘉县| 江源县| 武义县| 蓬溪县| 盱眙县| 阜城县| 金阳县| 通辽市| 得荣县| 德令哈市| 宁津县| 岗巴县| 谢通门县| 灵璧县| 祁阳县| 华容县| 鹤壁市| 鄂州市| 凤阳县| 永和县| 永年县| 民乐县| 新兴县| 禄丰县| 涪陵区| 马鞍山市| 正定县| 伊吾县| 彭泽县| 阿鲁科尔沁旗| 勃利县| 毕节市| 仪征市| 常山县| 延长县| 抚松县| 龙门县| 客服| 五大连池市| 枣庄市| 双峰县|