posts - 262,  comments - 221,  trackbacks - 0

          【GBK轉UTF-8】


          在很多論壇、網上經常有網友問“ 為什么我使用 new String(tmp.getBytes("ISO-8859-1"), "UTF-8") 或者 new String(tmp.getBytes("ISO-8859-1"), "GBK")可以得到正確的中文,但是使用 new String(tmp.getBytes("GBK"), "UTF-8") 卻不能將GBK轉換成UTF-8呢?”

          參考前面的
          【Java基礎專題】編碼與亂碼(03)----String的toCharArray()方法測試 一文,我們就知道原因了。因為如果客戶端使用GBK、UTF-8編碼,編碼后的字節經過ISO-8859-1傳輸,再用原來相同的編碼方式進行解碼,這個過程是“無損的轉換”---- 因為原始和最終的編碼方式相同。

          但是如果客戶端使用GBK編碼,到了服務器端要轉換成UTF-8,或者相反的過程。想一想,字節還是那些字節,但是編碼的規則變了。原來GBK編碼后的4個字節要用UTF-8的每個字符3個字節的規則編碼,怎么能不亂碼呢?

          所以從現在開始,不要再犯這種錯誤了。new String(tmp.getBytes("GBK"), "UTF-8") 這個過程,JVM內部是不會幫你自動對字節進行擴展以適應UTF-8的編碼的。正確的方法應該是根據UTF-8的編碼規則進行字節的擴充,即手動從2個字節變成3個字節,然后再轉換成十六進制的UTF-8編碼。

          在這個專題的第一篇文章
          【Java基礎專題】編碼與亂碼(01)---編碼基礎 開頭,我們就已經介紹了這個規則:
           ①得到每個字符的2進制GBK編碼
           ②將該16進制的GBK編碼轉換成2進制的字符串(2個字節)
           ③分別在字符串的首位插入110,在第9位插入10,在第17位插入10三個字符串,得到3個字節
           ④將這3個字節分別轉換成16進制編碼,得到最終的UTF-8編碼。




          下面給出一個從網絡上得到的Java轉碼方法,原文鏈接見:
          http://jspengxue.javaeye.com/blog/40781。下面的代碼做了小小的修改

          package example.encoding;

          /**
           * The Class CharacterEncodeConverter.
           
          */

          public class CharacterEncodeConverter {

              
          /**
               * The main method.
               * 
               * 
          @param args the arguments
               
          */

              
          public static void main(String[] args) {

                  
          try {
                      CharacterEncodeConverter convert 
          = new CharacterEncodeConverter();
                      
          byte[] fullByte = convert.gbk2utf8("中文");
                      String fullStr 
          = new String(fullByte, "UTF-8");
                      System.out.println(
          "string from GBK to UTF-8 byte:  " + fullStr);

                  }
           catch (Exception e) {
                      e.printStackTrace();
                  }

              }


              
          /**
               * Gbk2utf8.
               * 
               * 
          @param chenese the chenese
               * 
               * 
          @return the byte[]
               
          */

              
          public byte[] gbk2utf8(String chenese) {
                  
                  
          // Step 1: 得到GBK編碼下的字符數組,一個中文字符對應這里的一個c[i]
                  char c[] = chenese.toCharArray();
                  
                  
          // Step 2: UTF-8使用3個字節存放一個中文字符,所以長度必須為字符的3倍
                  byte[] fullByte = new byte[3 * c.length];
                  
                  
          // Step 3: 循環將字符的GBK編碼轉換成UTF-8編碼
                  for (int i = 0; i < c.length; i++{
                      
                      
          // Step 3-1:將字符的ASCII編碼轉換成2進制值
                      int m = (int) c[i];
                      String word 
          = Integer.toBinaryString(m);
                      System.out.println(word);

                      
          // Step 3-2:將2進制值補足16位(2個字節的長度) 
                      StringBuffer sb = new StringBuffer();
                      
          int len = 16 - word.length();
                      
          for (int j = 0; j < len; j++{
                          sb.append(
          "0");
                      }

                      
          // Step 3-3:得到該字符最終的2進制GBK編碼
                      
          // 形似:1000 0010 0111 1010
                      sb.append(word);
                      
                      
          // Step 3-4:最關鍵的步驟,根據UTF-8的漢字編碼規則,首字節
                      
          // 以1110開頭,次字節以10開頭,第3字節以10開頭。在原始的2進制
                      
          // 字符串中插入標志位。最終的長度從16--->16+3+2+2=24。
                      sb.insert(0"1110");
                      sb.insert(
          8"10");
                      sb.insert(
          16"10");
                      System.out.println(sb.toString());

                      
          // Step 3-5:將新的字符串進行分段截取,截為3個字節
                      String s1 = sb.substring(08);
                      String s2 
          = sb.substring(816);
                      String s3 
          = sb.substring(16);

                      
          // Step 3-6:最后的步驟,把代表3個字節的字符串按2進制的方式
                      
          // 進行轉換,變成2進制的整數,再轉換成16進制值
                      byte b0 = Integer.valueOf(s1, 2).byteValue();
                      
          byte b1 = Integer.valueOf(s2, 2).byteValue();
                      
          byte b2 = Integer.valueOf(s3, 2).byteValue();
                      
                      
          // Step 3-7:把轉換后的3個字節按順序存放到字節數組的對應位置
                      byte[] bf = new byte[3];
                      bf[
          0= b0;
                      bf[
          1= b1;
                      bf[
          2= b2;
                      
                      fullByte[i 
          * 3= bf[0];            
                      fullByte[i 
          * 3 + 1= bf[1];            
                      fullByte[i 
          * 3 + 2= bf[2];
                      
                      
          // Step 3-8:返回繼續解析下一個中文字符
                  }

                  
          return fullByte;
              }

          }

          最終的測試結果是正確的:string from GBK to UTF-8 byte:  中文。

          但是這個方法并不是完美的!要知道這個規則只對中文起作用,如果傳入的字符串中包含有單字節字符,如a+3中文,那么解析的結果就變成:string from GBK to UTF-8 byte:  ?????????中文了。為什么呢?道理很簡單,這個方法對原本在UTF-8中應該用單字節表示的數字、英文字符、符號都變成3個字節了,所以這里有9個?,代表被轉換后的a、+、3字符。

          所以要讓這個方法更加完美,最好的方法就是加入對字符Unicode區間的判斷

          UCS-2編碼(16進制) UTF-8 字節流(二進制)
          0000 - 007F 0xxxxxxx
          0080 - 07FF 110xxxxx 10xxxxxx
          0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx






          漢字的Unicode編碼范圍為\u4E00-\u9FA5 \uF900-\uFA2D,如果不在這個范圍內就不是漢字了。

          【UTF-8轉GBK】

          道理和上面的相同,只是一個逆轉的過程,不多說了


          但是最終的建議還是:能夠統一編碼就統一編碼吧!要知道編碼的轉換是相當的耗時的工作



          -------------------------------------------------------------
          生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
          posted on 2010-02-22 23:00 Paul Lin 閱讀(37026) 評論(11)  編輯  收藏 所屬分類: J2SE


          FeedBack:
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-02-24 05:25 | tbw
          學習了。  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-02-24 11:07 | 沒有腦和不高興
          tmp.getBytes("UTF-8")不就得了?  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-02-24 14:38 | Paul Lin
          @沒有腦和不高興

          你所說的tmp.getBytes("UTF-8");是直接從內存中取出Unicode,然后轉換成UTF-8編碼的字符,再得到該字符的字節數組。這個時候的編碼轉換流程是:

          Unicode字符 ---> encode字符 ---> encode byte[]

          而我文章中的內容是針對“原始數據以GBK格式傳輸,在目的端要轉換成UTF-8的格式保存”,從時機上說比你這個要早了一步,這個時候的編碼轉換流程是:

          GBK字符 ---> GBK字節---> 以ISO-8859-1 單字節傳輸 ---> 【轉換為UTF-8字節】---> UTF-8字符 ---> Unicode字符

          帶方括號的就是我文章中講的內容,請注意中間并沒有生成GBK字符。而是直接在字節級就進行轉換了。如果先解碼成GBK字符,再用UTF-8編碼,那就是你說的這種情況了。但是這樣就太繁瑣而且浪費內存空間了  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-02-25 22:12 | bat
          有的網站上說 UTF-8不能包括所有漢字 提倡用GBK 這個影響大嗎?  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-03-01 15:29 | 沒頭腦和不高興
          GBK字符 ---> GBK字節---> 以ISO-8859-1 單字節傳輸 ---> 【轉換為UTF-8字節】---> UTF-8字符 ---> Unicode字符

          GBK和UTF-8都是交換碼,只是字節,所謂的GBK字符是指誤將GBK當做ISO-8859-1轉換成的字符吧?
          那么還是new String(tmp.getBytes("ISO-8859-1"), "GBK").getBytes("UTF-8")  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2010-03-01 15:31 | 沒頭腦和不高興
          @bat
          UTF-8字匯和GB18030是兼容的,GB18030基本上是GBK的超集,僅規范了個別碼位。所以belabela
            回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換[未登錄]
          2010-03-02 09:35 | Paul Lin
          @沒頭腦和不高興

          GBK和UTF-8都是交換碼,只是字節,所謂的GBK字符是指誤將GBK當做ISO-8859-1轉換成的字符吧?
          ==================================================
          準確說不是誤傳,而是HTTP協議只支持使用ISO-8859-1的協議傳遞,客戶端的瀏覽器是按照GBK的編碼轉換成4個字節發出的。字節的內容和順序還是正確的。

          其次你說的交換碼是什么意思?請指教


          那么還是new String(tmp.getBytes("ISO-8859-1"), "GBK").getBytes("UTF-8")
          ==================================================
          你這個做法沒錯,最終得到的就是從Unicode--->UTF-8編碼,再用這個就可以構造出UTF-8的字符串了。

          但是我個人比較懶(^_^),其次我想節省內存空間(不生成臨時的GBK字符),所以就直接從字節開始轉了

            回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換[未登錄]
          2010-03-02 09:38 | Paul Lin
          @沒頭腦和不高興

          @bat
          UTF-8字匯和GB18030是兼容的,GB18030基本上是GBK的超集,僅規范了個別碼位。所以belabela
          ====================================================

          我的印象中,UTF-8是使用3個字節來表示中文的,GB18030是使用2個字節,他們對同一個漢字的編碼各自不同,你說的兼容是指那方面的呢?

          GB18030 》GB2312 》GBK 這個我就記得是兼容的  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2013-10-28 17:05 | 楊建群
          誰告訴你們utf8的漢字編碼就一定是3個字符啊??  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2014-01-07 17:31 | blues
          @楊建群
          是的,也有很多兩個的, 只是一部分超出的集合才用3個字節進行擴充的  回復  更多評論
            
          # re: 【Java基礎專題】編碼與亂碼(05)---GBK與UTF-8之間的轉換
          2014-06-18 18:09 | karl
          正常情況下,從外部來的數據都是二進制流。不是以String形式存在。最直觀的就是:從文件流讀取byte[]到java,然后進行編碼轉化。

          byte[] bytes = {FileInputStream從文件讀取二進制bytes};
          byte[] targetBytes = new String(bytes, sourceCharset).getBytes(targetCharset)

          這樣targetBytes就直接保存的就是目標charset的byte流。  回復  更多評論
            
          <2014年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(21)

          隨筆分類

          隨筆檔案

          BlogJava熱點博客

          好友博客

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 新绛县| 乳山市| 丹寨县| 米林县| 城市| 鄂托克前旗| 小金县| 永州市| 张掖市| 安平县| 潢川县| 宁河县| 临江市| 沂水县| 赣州市| 华池县| 铜山县| 扎兰屯市| 兴安县| 山丹县| 墨竹工卡县| 宜川县| 潼南县| 昭平县| 万安县| 邹平县| 长沙县| 黄骅市| 图木舒克市| 永宁县| 武汉市| 绥化市| 布尔津县| 桂东县| 于田县| 江源县| 邵武市| 那坡县| 同仁县| 邯郸市| 五寨县|