Java:編碼與亂碼問題


一、為什么要編碼?

由於人類的語言太多,因而表示這些語言的符號太多,無法用計算機的一個基本的存儲單元----byte來表示,因而必須要經過拆分或一些翻譯工作,才能讓計算機能理解。

byte一個字節即8個bit,所以能表示的字符范圍是0~255個,這滿足不了人類的需要,要解決這個矛盾必須需要一個新的數據結構char,從char到byte必須經過編碼。

 

二、常用編碼介紹

ASCII碼 

       總共128個,用一個字節的低7位表示,0~31是控制字符,如換行、回車、刪除等,32~126是打印字符,可以通過鍵盤輸入並且能夠顯示出來.

ISO-8859-1

       擴展自ASCII,仍然是單字節編碼,一共能表示256個字符

GB2312

       雙字節編碼。總編碼范圍A1~F7 ,其中A1~A9是符號區,包含682個符號,從B0~F7是漢字區,包含6763個漢字

GBK

       擴展自GB2312,能表示21003個漢字,其編碼和GB2312是兼容的。GBK的文字編碼是用雙字節來表示的,即不論中、英文字符均使用雙字節來表示,為了區分中文,將其最高位都設定成1。GBK包含全部中文字符

GB18030

       在實際應用系統中使用的並不廣泛

Unicode

       Unicode 是 Java 和 XML 的基礎,使用0~65 535的雙字節無符號數對每一個字符進行編碼

UTF-8

      是用以解決國際上字符的一種多字節編碼,它對英文使用8位(即一個字節),中文使用24為(三個字節)來編碼,使用Unicode編碼,一個英文字符要占用兩個字節,在Internet上,大多數的信息都是用英文來表示的,如果都采用Unicode編碼,將會使數據量增加一倍。為了減少存儲和傳輸英文字符數據的數據量,可以使用UTF-8編碼。

 

GBK、GB2312等與UTF8之間都必須通過Unicode編碼才能相互轉換:
GBK、GB2312--Unicode--UTF8
UTF8--Unicode--GBK、GB2312

 

三、對亂碼產生過程的分析

為了讓使用Java語言編寫的程序能在各種語言的平台下運行,Java在其內部使用Unicode字符集來表示字符,這樣就存在Unicode字符集和本地字符集進行轉換的過程。當在Java中讀取字符數據的時候,需要將本地字符集編碼的數據轉換為Unicode編碼,而在輸出字符數據的時候,則需要將Unicode編碼轉換為本地字符集編碼。 

例如,在中文系統下,從控制台讀取一個字符“中”,實際上讀取的是“中”的GBK編碼0xD6D0,在Java語言中要將GBK編碼轉換為Unicode編碼0x4E2D,此時,在內存中,字符“中”對應的數值就是0x4E2D,當我們向控制台輸出字符時,Java語言將Unicode編碼再轉換為GBK編碼,輸出到控制台,中文系統再根據GBK字符集畫出相應的字符。 

從上述過程來看,讀取和寫入的過程是可逆的,那么理應不會出現中文亂碼問題。然而,實際應用的情形,比上述過程要復雜得多。在Web應用中,通常都包括了瀏覽器、Web服務器、Web應用程序和數據庫等部分,每一部分都有可能使用不同的字符集,從而導致字符數據在各種不同的字符集之間轉換時,出現亂碼的問題。 

在Java語言中,不同字符集編碼的轉換,都是通過Unicode編碼作為中介來完成的。例如,GBK編碼的字符“中”要轉換為ISO-8859-1編碼,其過程如下: 

(1)因為在Java中的字符,都是用Unicode來表示的,所以GBK編碼的字符“中”要轉換為Unicode表示:0xD6D0->0x4E2D。 

(2)將字符“中”的Unicode編碼轉換為ISO-8859-1編碼,因為Unicode編碼0x4E2D在ISO-8859-1中沒有對應的編碼,於是得到0x3f,也就是字符“?”。 

下面的代碼演示了這一過程:

復制代碼
/GBK編碼的字符“中”轉換為Unicode編碼表示
String str="中"; 
//將字符“中”的Unicode編碼轉換為ISO-8859-1編碼 
byte[] b=str.getBytes("ISO-8859-1"); 
for(int i=0;i<b.length;i++) {       
        //輸出轉換后的二進制代碼。        
        System.out.print(b[i]); 
}
復制代碼

當從Unicode編碼向某個字符集轉換時,如果在該字符集中沒有對應的編碼,則得到0x3f(即問號字符?)。這就是為什么有時候我們輸入的是中文,在輸出時卻變成了問號。

 

從其他字符集向Unicode編碼轉換時,如果這個二進制數在該字符集中沒有標識任何的字符,則得到的結果是0xfffd。例如一個GBK的編碼值0x8140,從GB2312向Unicode轉換,然而由於0x8140不在GB2312字符集的編碼范圍(0xa1a1-0xfefe),當然也就沒有對應任何的字符,所以轉換后會得到0xfffd。

 

下面的代碼演示了這一過程。

復制代碼
        // 構造一個二進制數據。
        byte[] buf = { (byte) 0x81, (byte) 0x40, (byte) 0xb0, (byte) 0xa1 };
        // 將二進制數據按照GB2312向Unicode編碼轉換。
        String str = new String(buf, "GB2312");
        for (int i = 0; i < str.length(); i++) {
            // 取出字符串中的每個Unicode編碼的字符。
            char ch = str.charAt(i);
            // 將該字符對應的Unicode編碼以十六進制的形式輸出。
            System.out.print(Integer.toHexString((int) ch));
            System.out.print("--");
            // 輸出該字符。
            System.out.println(ch);
        }
復制代碼

四、web開發避免中文亂碼

1.Jquery的get、 post方式提交中文亂碼

 

Ajax方式提交,如果參數中帶有中文參數,最好就是指定頁面格式的編碼.Jquery這里默認使用utf-8的編碼

 值得注意的是: 在$.get()、$.post()方式中,要指定內容返回的內容的contentType格式.

 

復制代碼
get方法傳文字字符串就會有亂碼,因為是通過url傳參的。 
所以你要在js客戶端經過2次轉碼,同樣服務器端也要轉碼。 
$.get("AjaxService?userName=" + encodeURI(encodeURI(userName)), null, function (data) { 
$("#result").html(data); 
}); 
這2個效果是一樣的:encodeURIComponent(userName) 、 encodeURI(encodeURI(userName)) 
服務器端轉碼:String userName = URLDecoder.decode(request.getParameter("userName"), "UTF-8"); 
復制代碼

因為應用服務器會自動幫你做一次URLDecode,所以再加上你自己在代碼里面寫的URLDecode,一共就是兩個Decode了

一般情況下, 發送 encodeURIComponent(parmeName)+"="+encodeURIComponent(parmeValue);
接收時, 直接 String paramValue = request.getParameter(paramName); // 容器自動解碼.

我們知道 encodeURIComponent 使用的是 UTF-8 編碼規則來編的.
如果 request.getParameter(paramName) 時,容器也按 UTF-8 解的話,是正確的. 根本無須在客戶端
進行二次的 encodeURIComponent(...)


如果 request.getParameter(paramName),容器沒有按 UTF-8 解的話, 結果只有一個,就是亂碼!
容器按什么編碼來解碼,決定於 request.setCharacterEncoding(***) 或者 服務器程序配置.

如果你在 jsp 程序中,能夠 request.setCharacterEncoding("UTF-8"), 並且 修改服務器配置,讓容器在解 GET 提交的參數時,使用 UTF-8.

客戶端提交前不用二次編碼, 接收時,也只要直接 request.getParameter(paramName) 即可

 

 

Java、escape(str)和unescape(str);

 

對String對象進行編碼或者解碼,以便它們能在所有計算機上可讀;str中的非ASCII字符都是用【%xx】來表示的,其中xx表示該字符的16進制數,例如空格返回的是”%20”,字符值大於255的以%uxxxx格式存儲

 

 

 

注意:escape()方法不能夠對統一資源標識碼(URI)進行編碼,對其編碼應使用encodeURI()和encodeURIComponent()方法;

 

      unescape()方法不能解碼URI,解碼需使用decodeURI()和decodeURIComponent();

 

這里建議參考Commons-lang3包中的StringEscapeUtils

 

 

2.JSP與頁面參數傳參亂碼

 (1)頁面編碼不一致

<%@ page contentType="text/html; charset=gb2312"%>
...
<meta http-equiv="Content-Type" content="text/html charset=gb2312">
...

 

使用Servlet規范中的過慮器指定編碼,過濾器的在web.xml中的典型配置和主要代碼如下:

web.xml: 

復制代碼
 <filter>
   <filter-name>CharacterEncodingFilter</filter-name> 
<filter-class>cn.com.tony.web.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>GBK</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
復制代碼

CharacterEncodingFilter.java代碼段:

 

復制代碼
public class CharacterEncodingFilter implements Filter { 
    protected String encoding = null;
    public void init(FilterConfig filterConfig) throws ServletException   { 
        this.encoding = filterConfig.getInitParameter("encoding"); 
    } 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
        request.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset="+encoding);
        chain.doFilter(request, response);
    } 
} 
復制代碼

 

或者使用框架提供的亂碼過濾器如Spring

 

復制代碼
      <filter>
        <filter-name>EncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>GBK</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
復制代碼

 

3.鏈接傳參亂碼

 

在傳參的jsp對中文進行編碼:href="new.jsp?name=java.net.URLEncoder.encode("鏈接")";

在接受的jsp對中文進行轉碼:String str = URLDecoder.decode(request.getParameter("name "), "utf-8");

讀取的編碼格式要跟頁面中設置的編碼格式一致。

 

4.網頁編碼格式設置:

 

(1)、指定文件的存儲編碼,很明顯,該設置應該置於文件的開頭。例如:<%@page pageEncoding="GBK"%>,正常顯示中文,如果不設置默認是iso8859-1,它是不支持中文的。

 

(2)、jsp輸出,即:browser顯示網頁的時候,首先使用response.setCharacterEncoding()中指定的編碼,也可以是<%@ page contentType="text/html; charset= GBK" %>。如果未指定,則會使用網頁中meta項指定中的contentType。

 

(3)、 meta設置

指定網頁使用的編碼,該設置對靜態網頁尤其有作用。因為靜態網頁無法采用jsp的設置,而且也無法執行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" />而在jsp中meta的優先級最低,沒有以上兩種的編碼時才采用這中編碼推薦只用utf-8,它支持所有字符。

若是Servlet顯示網頁就用response.setContentType("text/html;charset=utf-8");

 

5.數據庫讀取亂碼

大部分數據庫都支持以unicode編碼方式,所以解決Java與數據庫之間的亂碼問題比較明智的方式是直接使用unicode編碼與數據庫交互。 很多數據庫驅動自動支持unicode,其他大部分數據庫驅動,可以在驅動的url參數中指定,如 mysql驅動:jdbc:mysql://localhost/MYTEST?useUnicode=true&characterEncoding=GBK。

 

6.瀏覽器編碼異常

 

亂碼問題
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>
......
<% 
//*****寫在首行*****//
request.setCharacterEncoding("UTF-8");
...
%>
在瀏覽器中,中文通過url傳遞都會默認被編碼,所以在url中的中文還必須編碼后再使用,

 

使用 <form>...</form> 提交的,瀏覽器才會編碼.

 

<form>提交時,瀏覽器使用什么編碼編,決定於 form 的 accept-charset 屬性(標准瀏覽器) 或者 document.charset(IE)
<form accept-charset="...." >...</form>

提交 application/x-form-www-encoded 表單時,瀏覽器 把所有參數 按  key=value 的形式組合
分別對 key, value 進行編碼.

多個參數間,用 "&" 連接, 如: key2=%E4%B8%AD%E6%96%87&key=AAAA&key=BBB&key1=CCC&%E6%B1%89%E5%AD%97=%E4%B8%AD%E6%96%87 
ajax 提交的,需要自己手動設置編碼

 

瀏覽器url編碼
eg : "2014中華小當家"先編碼成utf-8,再在url地址欄中傳遞,ie10地址欄輸入中文不會亂碼,火狐和chrome會

 

7.通過參數設置接口編碼

WebUtils.java

復制代碼
public static String getQueryValue(String queryString,String key){
        if(queryString==null || queryString.length()==0)return null;
        int reqIdx = queryString.indexOf(key);//req_enc=utf-8&resp_enc=gbk
        String enc = null;
        if(reqIdx!=-1){
            reqIdx = reqIdx+key.length();
            int endIdx = reqIdx;
            for (;
            endIdx < queryString.length() && queryString.charAt(endIdx)!='&';
            endIdx++) {
            }
            if(endIdx>reqIdx){
                enc = queryString.substring(reqIdx,endIdx);
                if(!enc.equalsIgnoreCase("gbk")
                        && !enc.equalsIgnoreCase("utf-8")){
                    enc = null;
                }else{
                    System.err.println("error :"+key+" is "+ enc);
                }
            }
        }
        return enc;
    }
復制代碼

setCharset.jsp

復制代碼
<%@ page contentType="text/html;charset=GBK"  %><%
String queryString = request.getQueryString();
String reqEnc = getQueryValue(queryString,"req_enc=");
if(reqEnc!=null){
    request.setCharacterEncoding(reqEnc);
}
String respEnc = getQueryValue(queryString,"resp_enc=");
if(respEnc!=null){
    response.setCharacterEncoding(respEnc);
}
%>
復制代碼

 

五、總結

上面提到的方法應該能解決大部分亂碼問題,如果在其他地方還出現亂碼,可能需要手動修改代碼。解決Java亂碼問題的關鍵在於在字節與字符的轉換 過程中,你必須知道原來字節或轉換后的字節的編碼方式,轉換時采用的編碼必須與這個編碼方式保持一致,否則將會出現Java文亂碼。

 

參考:

瀏覽器url編碼

 

解決JSP中文亂碼問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM