posts - 262,  comments - 221,  trackbacks - 0

          【GBK轉(zhuǎn)UTF-8】


          在很多論壇、網(wǎng)上經(jīng)常有網(wǎng)友問“ 為什么我使用 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轉(zhuǎn)換成UTF-8呢?”

          參考前面的
          【Java基礎(chǔ)專題】編碼與亂碼(03)----String的toCharArray()方法測(cè)試 一文,我們就知道原因了。因?yàn)槿绻蛻舳耸褂肎BK、UTF-8編碼,編碼后的字節(jié)經(jīng)過ISO-8859-1傳輸,再用原來相同的編碼方式進(jìn)行解碼,這個(gè)過程是“無損的轉(zhuǎn)換”---- 因?yàn)樵己妥罱K的編碼方式相同。

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

          所以從現(xiàn)在開始,不要再犯這種錯(cuò)誤了。new String(tmp.getBytes("GBK"), "UTF-8") 這個(gè)過程,JVM內(nèi)部是不會(huì)幫你自動(dòng)對(duì)字節(jié)進(jìn)行擴(kuò)展以適應(yīng)UTF-8的編碼的。正確的方法應(yīng)該是根據(jù)UTF-8的編碼規(guī)則進(jìn)行字節(jié)的擴(kuò)充,即手動(dòng)從2個(gè)字節(jié)變成3個(gè)字節(jié),然后再轉(zhuǎn)換成十六進(jìn)制的UTF-8編碼。

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




          下面給出一個(gè)從網(wǎng)絡(luò)上得到的Java轉(zhuǎn)碼方法,原文鏈接見:
          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編碼下的字符數(shù)組,一個(gè)中文字符對(duì)應(yīng)這里的一個(gè)c[i]
                  char c[] = chenese.toCharArray();
                  
                  
          // Step 2: UTF-8使用3個(gè)字節(jié)存放一個(gè)中文字符,所以長(zhǎng)度必須為字符的3倍
                  byte[] fullByte = new byte[3 * c.length];
                  
                  
          // Step 3: 循環(huán)將字符的GBK編碼轉(zhuǎn)換成UTF-8編碼
                  for (int i = 0; i < c.length; i++{
                      
                      
          // Step 3-1:將字符的ASCII編碼轉(zhuǎn)換成2進(jìn)制值
                      int m = (int) c[i];
                      String word 
          = Integer.toBinaryString(m);
                      System.out.println(word);

                      
          // Step 3-2:將2進(jìn)制值補(bǔ)足16位(2個(gè)字節(jié)的長(zhǎng)度) 
                      StringBuffer sb = new StringBuffer();
                      
          int len = 16 - word.length();
                      
          for (int j = 0; j < len; j++{
                          sb.append(
          "0");
                      }

                      
          // Step 3-3:得到該字符最終的2進(jìn)制GBK編碼
                      
          // 形似:1000 0010 0111 1010
                      sb.append(word);
                      
                      
          // Step 3-4:最關(guān)鍵的步驟,根據(jù)UTF-8的漢字編碼規(guī)則,首字節(jié)
                      
          // 以1110開頭,次字節(jié)以10開頭,第3字節(jié)以10開頭。在原始的2進(jìn)制
                      
          // 字符串中插入標(biāo)志位。最終的長(zhǎng)度從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:將新的字符串進(jìn)行分段截取,截為3個(gè)字節(jié)
                      String s1 = sb.substring(08);
                      String s2 
          = sb.substring(816);
                      String s3 
          = sb.substring(16);

                      
          // Step 3-6:最后的步驟,把代表3個(gè)字節(jié)的字符串按2進(jìn)制的方式
                      
          // 進(jìn)行轉(zhuǎn)換,變成2進(jìn)制的整數(shù),再轉(zhuǎn)換成16進(jìn)制值
                      byte b0 = Integer.valueOf(s1, 2).byteValue();
                      
          byte b1 = Integer.valueOf(s2, 2).byteValue();
                      
          byte b2 = Integer.valueOf(s3, 2).byteValue();
                      
                      
          // Step 3-7:把轉(zhuǎn)換后的3個(gè)字節(jié)按順序存放到字節(jié)數(shù)組的對(duì)應(yīng)位置
                      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:返回繼續(xù)解析下一個(gè)中文字符
                  }

                  
          return fullByte;
              }

          }

          最終的測(cè)試結(jié)果是正確的:string from GBK to UTF-8 byte:  中文。

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

          所以要讓這個(gè)方法更加完美,最好的方法就是加入對(duì)字符Unicode區(qū)間的判斷

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






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

          【UTF-8轉(zhuǎn)GBK】

          道理和上面的相同,只是一個(gè)逆轉(zhuǎn)的過程,不多說了


          但是最終的建議還是:能夠統(tǒng)一編碼就統(tǒng)一編碼吧!要知道編碼的轉(zhuǎn)換是相當(dāng)?shù)暮臅r(shí)的工作



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


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

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

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

          而我文章中的內(nèi)容是針對(duì)“原始數(shù)據(jù)以GBK格式傳輸,在目的端要轉(zhuǎn)換成UTF-8的格式保存”,從時(shí)機(jī)上說比你這個(gè)要早了一步,這個(gè)時(shí)候的編碼轉(zhuǎn)換流程是:

          GBK字符 ---> GBK字節(jié)---> 以ISO-8859-1 單字節(jié)傳輸 ---> 【轉(zhuǎn)換為UTF-8字節(jié)】---> UTF-8字符 ---> Unicode字符

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

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

          GBK和UTF-8都是交換碼,只是字節(jié),所謂的GBK字符是指誤將GBK當(dāng)做ISO-8859-1轉(zhuǎn)換成的字符吧?
          ==================================================
          準(zhǔn)確說不是誤傳,而是HTTP協(xié)議只支持使用ISO-8859-1的協(xié)議傳遞,客戶端的瀏覽器是按照GBK的編碼轉(zhuǎn)換成4個(gè)字節(jié)發(fā)出的。字節(jié)的內(nèi)容和順序還是正確的。

          其次你說的交換碼是什么意思?請(qǐng)指教


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

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

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

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

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

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

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

          這樣targetBytes就直接保存的就是目標(biāo)charset的byte流。  回復(fù)  更多評(píng)論
            
          <2014年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(21)

          隨筆分類

          隨筆檔案

          BlogJava熱點(diǎn)博客

          好友博客

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 满洲里市| 班玛县| 衡南县| 景德镇市| 和田市| 鹤峰县| 犍为县| 铁岭县| 德惠市| 梓潼县| 崇州市| 阜康市| 海盐县| 呼伦贝尔市| 财经| 乌拉特前旗| 城口县| 新安县| 平南县| 清丰县| 兴国县| 馆陶县| 芮城县| 天全县| 休宁县| 斗六市| 沧州市| 石阡县| 罗源县| 绥江县| 湖州市| 连平县| 武川县| 昌平区| 西华县| 大冶市| 台湾省| 吉林省| 弋阳县| 新兴县| 包头市|