隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0
          數據加載中……

          Java編碼問題解決方案大揭密

          本文為原創,如需轉載,請注明作者和出處,謝謝!

          一、Java編碼是怎么回事?

          對于使用中文以及其他非拉丁語系語言的開發人員來說,經常會遇到字符集編碼問題。對于Java語言來說,在其內部使用的是UCS2編碼(2個字節的Unicode編碼)。這種編碼并不屬于某個語系的語言編碼,它實際上是一種編碼格式的世界語。在這個世界上所有可以在計算機中使用的語言都有對應的UCS2編碼。

          正是因為Java采用了UCS2,因此,在Java中可以使用世界上任何國家的語言來為變量名、方法名、類起名,如下面代碼如下:


          class 中國
          {
              
          public String 雄起()
              {
                   
          return "中國雄起";
              }
          }

          中國 祖國 
          = new 中國();
          System.out.println(祖國.雄起());

              哈哈,是不是有點象“中文編程”。實際上,也可以使用其他的語言來編程,如下面用韓文和日文來定義個類:

          class ???
          {
              
          public void スーパーマン() {  }
          }

              實際上,由于Java內部使用的是UCS2編碼格式,因為,Java并不關心所使用的是哪種語言,而只要這種語言在UCS2中有定義就可以。

              UCS2編碼中為不同國家的語言進行了分頁,這個分頁也叫“代碼頁”或“編碼頁”。中文根據包含中文字符的多少,分了很多代碼頁,如cp935cp936等,然而,這些都是在UCS2中的代碼頁名,而對于操作系統來說,如微軟的windows,一開始的中文編碼為GB2312,后來擴展成了GBK。其實GBKcp936是完全等效的,用它們哪個都行。

          二、Java編碼轉換

             
          上面說了這么多,在這一部分我們做一些編碼轉換,看看會發生什么事情。

              先定義一個字符串變量:

              String gbk = "
          中國"; // “中國”在Java內部是以UCS2格式保存的

              用下面的語言輸出一定會輸出中文:

          System.out.println(gbk);

              實現上,當我們從IDE輸入“中國”時,用的是java源代碼文件保存的格式,一般是GBK,有時也可是utf-8,而在Java編譯程序時,會不由分說地將所有的編碼格式轉換成utf-8編碼,讀者可以用UltraEdit或其他的二進制編輯器打開上面的“中國.class”,看看所生成的二進制是否有utf-8的編碼(utf-8ucs2之間的轉換非常容易,因為utf-8ucs2之間是用公式進行轉換的,而不是到代碼頁去查,這就相當于將二進制轉成16進制一樣,4個字節一組)。如“中國”的utf-8編碼按著GBK解析就是“涓  浗”。如下圖所示。



          如果使用下面的語言可以獲得“中國”的utf-8字節,結果是6(一個漢字由3個字節組成)

          System.out.println(gbk.getBytes("utf-8").length);

          下面的代碼將輸出“涓  浗”。

          System.out.println(new String(gbk.getBytes("utf-8"), "gbk"));   

          由于將“中國“的utf-8編碼格式按著gbk解析,所以會出現亂碼。

          如果要返回中文的UCS2編碼,可以使用下面的代碼:

          System.out.println(gbk.getBytes("unicode")[2]);

          System.out.println(gbk.getBytes("unicode")[3]);

          前兩個字節是標識位,要從第3個字節開始。還有就是其他的語言使用的編碼的字節順序可能不同,如在C#中可以使用下面的代碼獲得“中國“的UCS2編碼:

          String s = "
          ";

          MessageBox.Show(ASCIIEncoding.Unicode.GetBytes(s)[0].ToString());

          MessageBox.Show(ASCIIEncoding.Unicode.GetBytes(s)[1].ToString());

              使用上面的java代碼獲得的“中“的16進制UCS2編碼為4E2D,而使用C#獲得的相應的ucs2編碼為2D4E,這只是C#Java編碼內部使用的問題,并沒有什么關系。但在C#Java互操作時要注意這一點。

              如果使用下面的java編碼將獲得16進制的“中”的GBK編碼:

          System.out.println(Integer.toHexString(0xff & xyz.getBytes("gbk")[0]));

          System.out.println(Integer.toHexString(0xff & xyz.getBytes("gbk")[1]));

          “中”的ucs2編碼為2D4EGBK編碼為D6D0

              讀者可訪問如下的url自行查驗:

              http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP936.TXT

              當然,感興趣的讀者也可以試試其他語言的編碼,如“人類”的韓語是“???”,如下面的代碼將輸出“???”的cp949ucs2編碼,其中cp949是韓語的代碼頁。


          String korean = "???"// 共三個韓文字符,我們只測試第一個“?”

          System.out.println(Integer.toHexString(
          0xff & korean.getBytes("unicode")[2]));

          System.out.println(Integer.toHexString(
          0xff & korean.getBytes("unicode")[3]));

          System.out.println(Integer.toHexString(
          0xff & korean.getBytes("Cp949")[0]));

          System.out.println(Integer.toHexString(
          0xff & korean.getBytes("Cp949")[1]));

           

          上面代碼的輸出結果如下:

          c7

          78

          c0

          ce

              也就是說“?”的ucs2編碼為C778cp949的編碼為C0CE,要注意的是,在cp949中,ucs2編碼也有C0CE,不要弄混了。讀者可以訪問下面的url來驗證:

          http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP949.TXT

          Java支持的編碼格式

          三、屬性文件

          Java中的屬性文件只支持iso-8859-1編碼格式,因此,要想在屬性文件中保存中文,就必須使用UCS2編碼格式("uxxxx),因此,出現了很多將這種編碼轉換成可視編碼和工具,如Eclipse中的一些屬性文件編輯插件。

          實際上,"uxxxx編碼格式在javaC#中都可以使用,如下面的語句所示:

          String name= ""u7528"u6237"u540d"u4e0d"u80fd"u4e3a"u7a7a" ;

          System.out.println(name);

              上面代碼將輸出“用戶名不能為空”的信息。將"uxxxx格式顯示成中文非常簡單,那么如何將中文還原成"uxxxxx格式呢?下面的代碼完成了這個工作:


          String ss = "用戶名不能為空";
          byte[] uncode = ss.getBytes("Unicode");
          int x = 0xff;
          String result 
          ="";
          for(int i= 2; i < uncode.length; i++)
          {
              
          if(i % 2 == 0) result += "\\u";
              String abc 
          = Integer.toHexString(x & uncode[i]);            
              result 
          += abc.format("%2s", abc).replaceAll(" ""0");               
          }
          System.out.println(result);

           

              上面的代碼將輸出如下結果:


          \u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a

              好了,現在可以利用這個技術來實現一個屬性文件編輯器了。

          四、Web中的編碼問題

              大家碰到最多的編碼問題就是在Web應用中。先讓我們看看下面的程序:

           

          <!--  main.jsp  -->

            
          <%@ page language="java"  pageEncoding="utf-8"%>

            
          <html>
                
          <head>

                
          </head>

                
          <body>
                    
          <form action="servlet/MyPost" method="post">
                        
          <input type="text" name="user" />
                        
          <p/>
                        
          <input type="submit"  value="提交"/>
                    
          </form>

                
          </body>
            
          </html>


              下面是個Servlet

            package servlet;

            import java.io.IOException;
            import java.io.PrintWriter;
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServlet;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;

            
          public class MyPost extends HttpServlet
            {

                
          public void doPost(HttpServletRequest request, HttpServletResponse response)
                        throws ServletException, IOException
                {
                    String user 
          = request.getParameter("user");
                    System.
          out.println(user);
                }
            }


              如果中main.jsp中輸入中文后,向MyPost提交,在控制臺中會輸出“中å?½”,一看就是亂碼。如果將IE的當前編碼設成其他的,如由utf-8改為gbk,仍然會出現亂碼,只是亂得不一樣而已。這是因為客戶端提交數據時是根據瀏覽器當前的編碼格式來提交的,如瀏覽器當前為gbk編碼,就以gbk編碼格式來提交。 這本身是不會出現亂碼的,問題就出在Web服務器接收數據的時候,HttpServletRequest在將客戶端傳來的數據轉成ucs2上出了問題。在默認情況下,是按著iso-8859-1編碼格式來轉的,而這種編碼格式并不支持中文,所以也就無法正常顯示中文了,解決這個問題的方法是用和客戶端瀏覽器當前編碼格式一致的編碼來轉換,如果是utf-8,則在doPost方法中應該用以下的語句來處理:

              request.setCharacterEncoding("utf-8");

              為了對每一個Servlet都起作用,可以將上面的語句加到filter里。

              另外,我們一般使用象MyEclipse一樣的IDE來編寫jsp文件,這樣的工具會根據pageEncoding屬性將jsp文件保存成相應的編碼格式,但如果要使用象記事本一樣的簡單的編輯器來編寫jsp文件,如果pageEncodingutf-8,而在默認時,記事本會將文件保存成iso-8859-1ascii)格式,但在myeclipse里,如果文件中有中文,它是不允許我們保存成不支持中文的編碼格式的,但記事本并不認識jsp,因此,這時在ie中就無法正確顯示出中文了。除非用記事本將其保存在utf-8格式。如下圖:


           

           





          Android開發完全講義(第2版)(本書版權已輸出到臺灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2008-07-19 13:45 銀河使者 閱讀(8237) 評論(16)  編輯  收藏 所屬分類: java 、web 原創

          評論

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          good
          2008-07-19 14:54 | Skywalker

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          厲害 收藏!
          2008-07-19 15:02 | BeanSoft

          # re: Java編碼問題解決方案大揭密[未登錄]  回復  更多評論   

          LZ研究的還不錯。不過研究的還是不夠深入啊。
          說說Web中的情況:
          request.setCharacterEncoding("utf-8");這個方法對與Tomcat這樣的服務器,有些版本就是不起作用的啦。
          還有個,就是你的jsp例子中的FORM提交,LZ不知道有沒有研究過JavaScript中的提交情況,其實你也可以在網頁中的charset不是UTF-8的情況下,將所有傳到服務器的內容全部編碼成UTF-8的。這個需要用到JavaScript的encodeURIComponent方法,具體的用法可以GOOGLE一下。
          其實關于亂碼有更好的解決方法,給大家提個思路:
          在客戶端將所有的中文全部編碼成一種可反編碼的編碼,比如Unicode的碼,用一定的標識符間隔區分,然后在服務器端將接收到的編碼過的內容再反編碼成實際的內容。這樣就可以忽略編碼的不同了……
          2008-07-19 16:36 | James

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          @James
          不好意思,我的程序都是在tomcat6里調試通過的,再說這些都是servlet規范規定的,所有的web服務器必須遵循。如果某個web服務器不遵循,那么web程序也就無法在其上正常運行了。

          web亂碼的問題非常多
          2008-07-19 16:46 | 銀河使者

          # re: Java編碼問題解決方案大揭密[未登錄]  回復  更多評論   

          LZ可以試驗一下Tomcat5,5當中設置request.setCharacterEncoding("utf-8")就是沒有效果的。
          前段時間一直在搞編碼的統一問題,測試過程中遇到的情況。這樣設置就沒有效果
          2008-07-19 16:50 | James

          # re: Java編碼問題解決方案大揭密[未登錄]  回復  更多評論   

          補充一個,Servlet是規定了,不過有些服務器在實現這些Servlet的時候就不遵循啊,這讓開發人員很是郁悶啊。
          比如,response.setCharacterEncoding(String charset);這個方法在有些版本的WebLogic服務器中就沒有實現它,結果導致你使用這個方法的話,在某版本的Weblogic服務器中就沒有效果,而且會拋Error
          2008-07-19 16:52 | James

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          @James

          我的程序是在tomcat6調試通過的,你說的可能是tomcat5以前的版本,但那應該是這些版本的bug,從理論上,是必須支持這個功能的,否則web程序是無法在這些web服務器上運行的。但很遺憾,我沒有碰到過這些版本的tomcat。

          至于web亂碼問題,我只是簡單提了一下,本文的目的并不是解決web亂碼問題。而是讓讀者了解java內部編碼的問題。如果了解了java編碼原理,你認為web亂碼問題,甚至其他的亂碼問題還能算是問題嗎?

          你說的沒錯,用form提交是產生亂碼問題的一種原因,而用httpxmlrequest提交也會產生亂碼。這種情況下最好使用encodeURI或encodeURIComponent將中文編成%xxxx的形式,然后在服務端使用java.net.URLDecoder.decode方法進行解碼。

          象在struts里的actionform,都可能會產生亂碼問題。

          還有就是在http請求頭或響應頭中傳中文,也會出現亂碼問題。這些只要了解了java的編碼體系,都可以迎刃而解。


          最后總結一下:

          一般編碼傳送可以采用兩種方式:
          1、直接編碼。就是采用utf-8、gbk等形式。這樣在服務端可以使用setCharacterEncoding指定相應的編碼。
          2、使用url形式的編碼。如%xxxx
          如果使用<form>的話,會根據當前瀏覽器的編碼確定發送的中文編碼。
          如果用javascript,會以utf-8編碼發送。
          2008-07-19 16:59 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          如果response.setCharacterEncoding不好使,可以試試response.setContentType("text/html;charset=utf-8");

          如果都不好使,就想別的方法,方法還是有的呢,可以用%xxxx形式,或是看一下setCharacterEncoding是怎么弄的,自已處理一下。
          2008-07-19 17:05 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          沒有辦法,由于做服務器的大多都是老外,它們永遠不會有編碼問題的,所以估計request.setCharacterEncoding方法就成了可選實現的功能的,但我想國產的web服務器應該都支持,如apusic 。
          2008-07-19 17:06 | 銀河使者

          # re: Java編碼問題解決方案大揭密[未登錄]  回復  更多評論   

          @銀河使者
          你總結的各種編碼的原理和過程很不錯,對Java的編碼也是研究頗多。
          只是你說的解決方案不是一個通用的解決之道,要針對不同的情況來區別對待。
          下面是我針對你采用的兩種方式的一點提醒,希望對看到這篇文章的人有點用
          1、直接編碼。服務器端使用setCharacterEncoding指定相應的編碼,注意服務器的版本,比如Tomcat5.0版本中就不支持這個方法,設置后會沒有任何效果,使用的時候注意測試一下。如果不行,解決方法之一是換高版本的Tomcat,第二是用編碼轉換,在Tomcat中的默認流編碼是ISO-8859-1的,只用進行一個轉碼操作也就可以了。例:String pipeStr = new String(rawStr.getBytes("ISO-8859-1"), "UTF-8");//UTF-8可以改成自己想要的編碼
          2、如果代碼中拼湊的URL用到中文字符的時候,可以考慮用一個encodeURI或encodeURIComponent,方便服務器端進行統一的UTF-8解碼。如果JavaScript與服務器端進行交互,需要對返回的數據,比如JSON數據進行處理的話,在服務器端需要對返回數據做好編碼工作,比如是通過response寫出的,需要加response.setContentType("charset=XXX");來保證返回的數據的編碼,使得JavaScript能夠正常解析。另外,如果有JavaScript文件是通過服務器端的Servlet動態生成再傳回客戶端的話,建議JavaScript的編碼應于客戶端的頭中的編碼一致,不然會出現JavaScript文件中的內容亂碼的現象,盡管JavaScript某些時候能正常使用,但是還是沒有亂碼的好啊。

          PS:銀河使者 對編碼研究的挺深,有機會可以交流心得……
          2008-07-19 17:24 | James

          # re: Java編碼問題解決方案大揭密[未登錄]  回復  更多評論   

          現在主流的服務器都是國外的,是苦了我們啊,沒辦法啊……
          國內的服務器做的好的又有幾個呢?apusic還可以,可是畢竟力量還是不夠強大啊。
          大家也就是總結這些東西的使用經驗和注意點……
          2008-07-19 17:31 | James

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          @ James
          不錯,你的第一種方法是比我的通用,因為你的方法是通過String類進行編碼轉換的,而我的是通過web服務器所提供的setCharacterEncoding方法實現編碼轉換的。由于String類是jdk的標準類,所以這種轉換方式和web服務器無關,任何基于java的程序(包括桌面程序)都可以使用這種方式進行轉換。
          而且將其以ISO-8859-1取出,實際上用iso-8859-1往外取字節,就相當于是不經過轉換,直接取出來了,如將“中國"的utf-8編碼“0xe4, 0xb8, 0xad,0xe5,0x9b, 0xbd”直接用iso-8859-1保存在String中(注意,不是將其轉換成ucs2,而是直接用utf-8共6個字節將其保存在String中),代碼如下:


          byte[] utf8 = new byte[]{(byte)0xe4, (byte)0xb8, (byte)0xad,(byte) 0xe5, (byte)0x9b, (byte)0xbd};
          String zg = new String(utf8, "iso-8859-1");
          String sss = new String(zg.getBytes("iso-8859-1"), "utf-8"); // 不能用utf-8
          String zg1 = new String(sss.getBytes("utf-8"), "utf-8");
          System.out.println(sss);
          System.out.println(zg1);

          其中 String zg = new String(utf8, "iso-8859-1");的作用就是將“中國”的6個utf-8編碼直接保存在String中,在這種情況下,不能使用zg.getBytes("utf-8")獲得字節,因為getBytes方法功能是將String中的ucs2編碼(4個字節)轉換成utf-8編碼的6個字節,而現在String中是6個字節的utf-8,而不是4個字節的ucs2,如果這時再用utf-8的話,java就會將這6個字節的utf-8編碼當成了3個ucs2編碼(2個字節為一個ucs2編碼),所以就會出現亂碼了。

          而用zg.getBytes("iso-8859-1")就是將這6個字節的utf-8編碼按原樣取出,然后用 new String(zg.getBytes("iso-8859-1"), "utf-8");將這6個字節按著utf-8格式轉換成了java內部使用的ucs2編碼。(實際上utf-8編碼并沒有真正轉化為ucs2,因為這樣太占資源了,在程序中還可能有很多英文字符,因此,utf-8就直接放到那了,反正將它轉換成ucs也很容易)。

          而在sss中的編碼就已經是utf-8(ucs2)了,因此,必須使用下面的代碼獲得字節數組:
          sss.getBytes("utf-8")
          2008-07-19 18:30 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          要注意的一點就是getBytes和String都具有編碼轉換功能。
          getBytes是將ucs2轉換成其他的編碼,而String是將其他的編碼轉換成ucs2編碼
          2008-07-19 18:41 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          如果直接用getBytes("unicode"),就直接把ucs2編碼得出來了。

          如果用下面的代碼:
          zg.getBytes("unicode");

          由于zg是以iso-8859-1保存的,因此,按著字節輸出,就會有下面的結果:

          0
          e4
          0
          b8
          0
          ad
          0
          e5
          0
          9b
          0
          bd

          這也說明iso-8859-1的編碼轉換成ucs2后,第一個字節補0
          2008-07-19 18:42 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          我看到網上關于在servlet中向客戶端輸出中文字符時亂碼解決方案一般是通過如下的代碼解決:

          response.setCharacterEncoding("utf-8");

          response.setContentType("text/html;charset=utf-8");

          但關鍵是web服務器轉換字節時是否讀取了這個設置的字符集編碼呢?如果未讀取,等于沒設。所以最通用的方法是采用如同的代碼解決向客戶端中文亂碼的問題:

          String ss = "中華人民共和國";

          String utf8 = new String(ss.getBytes("utf-8"), "iso-8859-1");
          response.setCharacterEncoding("iso-8859-1");
          response.getWriter().write(utf8);

          2008-07-19 19:13 | 銀河使者

          # re: Java編碼問題解決方案大揭密  回復  更多評論   

          慢慢看 編碼這個問題在中文特別嚴重
          2008-07-19 22:15 | 黑蝙蝠
          主站蜘蛛池模板: 永寿县| 江津市| 耒阳市| 刚察县| 商城县| 巴楚县| 兰州市| 会宁县| 海安县| 兰溪市| 新昌县| 沧州市| 乐东| 天镇县| 调兵山市| 南部县| 文成县| 孙吴县| 兰溪市| 斗六市| 县级市| 静海县| 临沧市| 乐平市| 石河子市| 皋兰县| 大名县| 邵阳县| 玛多县| 大庆市| 郧西县| 合水县| 白河县| 鄂尔多斯市| 东海县| 宁城县| 昔阳县| 洪洞县| 金川县| 安陆市| 鄂伦春自治旗|