編碼、亂碼問題

原文: http://gsdhaiji-cai.iteye.com/blog/1148049html

1、編碼進程

【01編碼】——好久好久之前,爲了表示二極管的通、分,咱們引入的高電平、低電平,以後又引入的一、0編碼進行代替java

 

【ASCII編碼】——好久之前,也就是上個世紀60年代,美國佬爲了把計算機的「0101010」編碼與文字進行對應起來,制定了一套ASCII編碼方案。人老是自私的,他只對本身的語言進行編碼,26個字母、數字、其餘符號,只用了7位二進制數搞定,第一位用0表示,預留着。因此ASCII編碼最多127編碼mysql

 

【「二代」ASCII編碼】——不僅僅是美國佬想把計算機語言和英文聯繫起來,其餘國家也想把本身的語言關聯起來,我法文、俄文就基於ASCII編碼,利用上ASCII第一位(未使用)變成1,來對本身國家的語言進行編碼。linux

 

介樣,每個國家都基於ASCII,讓第一位變成1,完成對本國的語言的編碼。因爲他們各幹各的,沒有溝通,從而致使了同一種編碼出現不一樣的文字。程序員

 

【gb2312編碼】——咱們偉大的祖國80年代也開始對漢字進行編碼,因爲咱們的文字較多,即便是基於ASCII將第一位變成1,也不夠咱們使用(國語博大精深拉),因此就制定了一套gb2312編碼,使用2個字節表示。web

 

【BIG5編碼】——我國的港澳地區,他們是使用繁體字(gb2312編碼最初並無考慮到繁體字),怎麼辦了?他們就出了本身的區域編碼BIG5ajax

 

【中國一統GBK編碼】——爲了統一漢字,迫切的須要設計出一種既能支持簡體字又能表示繁體的新編碼方案,GBK誕生了!他兼容了絕大部分gb2312編碼(gb2312編碼的文字用gbk能夠讀出來,可是不兼容BIG5編碼)sql

 

【世界大統unicode編碼】——各國編碼各作各的,總不是意見好事,爲了便於交流,國際社會引入了unicode——(uni統一的意思,code編碼)把全部國家的文字都進行了編碼。數據庫

 

【統一後浪費空間問題】——統一是一件好事,可是也是有問題的。英文只要1個字節ASCII編碼就能夠表示,你unicode還須要2個字節或更多,致使unicode表示英文的時候前面有不少無用的000000000000000000,對吧。編程

 

【解決問題,UTF編碼】——解決方法是使用utf8編碼,它是基於unicode編碼上的一種優化,英文使用1個字節,中文使用二、3個(絕大部分是3個,個別有看到二、4個的通常不用)字節。好處:我能屈能伸,我能夠變化長度來保存,就不會浪費空間了吧。

 

 

 

 

來看看unicode和utf對應吧

<!--[if !supportLists]-->·         <!--[endif]-->0000 0000-0000 007F | 0xxxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0000 0080-0000 07FF | 110xxxxx 10xxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

<!--[if !supportLists]-->·         <!--[endif]-->0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

舉例說明:

「漢」字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001,用這個比特流依次代替模板後面的x,獲得:11100110 10110001 10001001,十六進制E6 B1 89,轉換成10進制就是230  這個就是咱們的UTF8編碼,"漢".getBytes("utf-8") 獲得-26 -79 -119,是負數,也是跟這裏有關

漢      Unicode        utf8

        0x6c49         0xe6b189

2、java編程的轉碼

   1、JVM內存中保存的文字編碼

       從源文件àclass文件過程當中的轉碼狀況。最終的class文件都是以unicode編碼的,咱們前面所作的工做就是把各類不一樣的編碼轉換爲unicode編碼,好比從GBK轉換爲unicode,從UTF-8轉換爲unicode。由於只有採用正確的編碼來轉碼才能保證不出現亂碼。Jvm在運行時其內部都是採用unicode編碼的,其實在輸出時,又會作一次編碼的轉換。

 

好比:Sysout.out.println(「咱們」)。通過正確的解碼後」咱們」是unicode保存在內存中的,可是在向標準輸出(控制檯)輸出時,jvm又作了一次轉碼,它會採用操做系統默認編碼(中文操做系統是GBK),將內存中的unicode編碼轉換爲GBK編碼,而後輸出到控制檯。

參考文獻:http://www.blogjava.net/zhangchao/archive/2011/05/26/351051.html

 

   2、愛也getBytes,恨也getBytes

       通常咱們是經過getBytes來進行編碼,以下

         public byte[] getBytes(String charsetName)       //使用規定的編碼

         public byte[] getBytes(Charset charset) //使用規定的編碼

         public byte[] getBytes() //使用啓動JVM設置的編碼(若是沒有設置通常是系統默認編碼),避免使用,默認編碼和平臺有關

 

    通常咱們經過new String來進行解碼,以下

       public String(byte bytes[], String charsetName)        

                  public String(byte bytes[], Charset charset)

                  public String(byte bytes[])

 

    原則1:使用某一種編碼集對文字進行編碼,就得使用同一種編碼集進行解碼

       好比:「漢」是在java文件中的,此java文件是utf-8或者gbk編碼的

         getBytes使用utf-8編碼,new String使用utf-8解密

    new String("漢".getBytes("utf-8"), "utf-8"));

       

    原則2:看清楚對方給你的是什麼編碼,使用逆過程進行編碼、解碼(像出棧操做),最終獲得你想要文字

    好比:對方進行了以下操做,將「漢」進行utf-8編碼,而後使用iso8859-1進行解碼生成對應的文字:,最後經過文本發送給你。對方可能的操做應該是:

         String x = new String("漢".getBytes("utf-8"), "iso8859-1");

 

         咱們須要對文本中文字x:進行iso8859-1編碼,獲得的是「漢」utf-8的譯碼,而後咱們在對「漢」進行解碼獲得其utf8的文字

    new String(x.getBytes("iso8859-1"),"utf-8"));

 

    原則3:慎用getBytes(),他是基於平臺的,你得看看JVM啓動的時候file.encoding這個屬性值究竟是什麼(請注意tomcat的啓動JVM時候設置的編碼)

    源碼:特別注意黃底部分

staticbyte[] encode(char[] ca, int off, int len) {

    String csn = Charset.defaultCharset().name();

    try {

        return encode(csn, ca, off, len);

    } catch (UnsupportedEncodingException x) {

        warnUnsupportedCharset(csn);

    }

    try {

        return encode("ISO-8859-1", ca, off, len);

    } catch (UnsupportedEncodingException x) {

        // If this code is hit during VM initialization, MessageUtils is

        // the only way we will be able to get any kind of error message.

        MessageUtils.err("ISO-8859-1 charset not available: "

                + x.toString());

        // If we can not find ISO-8859-1 (a required encoding) then things

        // are seriously wrong with the installation.

        System.exit(1);

        returnnull;

    }

publicstatic Charset defaultCharset() {

        if (defaultCharset == null) {

        synchronized (Charset.class) {

       java.security.PrivilegedAction pa =

           new GetPropertyAction("file.encoding");

       String csn = (String)AccessController.doPrivileged(pa);

       Charset cs = lookup(csn);

       if (cs != null)

           defaultCharset = cs;

                else

           defaultCharset = forName("UTF-8");

            }

    }

    return defaultCharset;

}

}

-Dfile.encoding解釋:
在命令行中輸入java,在給出的提示中會出現-D的說明:
-D<name>=<value>
set a system property
-D後面須要跟一個鍵值對,做用是經過命令行向java虛擬機傳遞一項系統屬性
對-Dfile.encoding=UTF-8來講就是設置系統屬性file.encoding爲UTF-8

 

在java.nio.charset包中的Charset.java中。這段話的意思說的很明確了,簡單說就是默認字符集是在java虛擬機啓動時決定的,後面沒法動態改變了。默認字符集就是從file.encoding這個屬性中獲取的。

 

Java's file.encoding property on Windows platform
This property is used for the default encoding in Java, all readers and writers would default to using this property. file.encoding is set to the default locale of Windows operationg system since Java 1.4.2. System.getProperty("file.encoding") can be used to access this property. Code such as System.setProperty("file.encoding", "UTF-8") can be used to change this property. However, the default encoding can be not changed dynamically even this property can be changed. So the conclusion is that the default encoding can't change after JVM starts. java -dfile.encoding=UTF-8 can be used to set the default encoding when starting a JVM. I have searched for this option Java official documentation. But I can't find it.

 

參考文獻:http://www.cnblogs.com/vigarbuaa/archive/2012/04/11/2442582.html

3、url網址的編碼規範

1、url編碼規範

通常來講,URL只能使用英文字母、阿拉伯數字和某些標點符號,不能使用其餘文字和符號。好比,世界上有英文字母的網址「http://www.abc.com」,可是沒有希臘字母的網址「http://www.aβγ.com」(讀做阿爾法-貝塔-伽瑪.com)。這是由於網絡標準RFC 1738作了硬性規定:

"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."

「只有字母和數字[0-9a-zA-Z]、一些特殊符號「$-_.+!*'(),」[不包括雙引號]、以及某些保留字,才能夠不通過編碼直接用於URL。」

這意味着,若是URL中有漢字,就必須編碼後使用。可是麻煩的是,RFC 1738沒有規定具體的編碼方法,而是交給應用程序(瀏覽器)本身決定。這致使「URL編碼」成爲了一個混亂的領域。

 

參考文獻:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

4、瀏覽器的編碼

下面就讓咱們看看,「URL編碼」到底有多混亂。我會依次分析四種不一樣的狀況,在每一種狀況中,瀏覽器的URL編碼方法都不同。把它們的差別解釋清楚以後,我再說如何用Javascript找到一個統一的編碼方法。

2、狀況1:網址路徑中包含漢字

打開IE(我用的是8.0版),輸入網址「http://zh.wikipedia.org/wiki/春節」。注意,「春節」這兩個字此時是網址路徑的一部分。

 

查看HTTP請求的頭信息,會發現IE實際查詢的網址是「http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82」。也就是說,IE自動將「春節」編碼成了「%E6%98%A5%E8%8A%82」。

 

咱們知道,「春」和「節」的utf-8編碼分別是「E6 98 A5」和「E8 8A 82」,所以,「%E6%98%A5%E8%8A%82」就是按照順序,在每一個字節前加上%而獲得的。(具體的轉碼方法,請參考我寫的《字符編碼筆記》。)

在Firefox中測試,也獲得了一樣的結果。因此,結論1就是,網址路徑的編碼,用的是utf-8編碼。

3、狀況2:查詢字符串包含漢字

在IE中輸入網址「http://www.baidu.com/s?wd=春節」。注意,「春節」這兩個字此時屬於查詢字符串,不屬於網址路徑,不要與狀況1混淆。

 

查看HTTP請求的頭信息,會發現IE將「春節」轉化成了一個亂碼。

 

切換到十六進制方式,才能清楚地看到,「春節」被轉成了「B4 BA BD DA」。

 

咱們知道,「春」和「節」的GB2312編碼(個人操做系統「Windows XP」中文版的默認編碼)分別是「B4 BA」和「BD DA」。所以,IE實際上就是將查詢字符串,以GB2312編碼的格式發送出去。

Firefox的處理方法,略有不一樣。它發送的HTTP Head是「wd=%B4%BA%BD%DA」。也就是說,一樣採用GB2312編碼,可是在每一個字節前加上了%。

 

因此,結論2就是,查詢字符串的編碼,用的是操做系統的默認編碼。(我使用谷歌瀏覽器,他會是使用utf-8編碼的)

4、狀況3Get方法生成的URL包含漢字

前面說的是直接輸入網址的狀況,可是更常見的狀況是,在已打開的網頁上,直接用Get或Post方法發出HTTP請求。

根據臺灣中興大學呂瑞麟老師的試驗,這時的編碼方法由網頁的編碼決定,也就是由HTML源碼中字符集的設定決定。

  <meta http-equiv="Content-Type" content="text/html;charset=xxxx">

若是上面這一行最後的charset是UTF-8,則URL就以UTF-8編碼;若是是GB2312,URL就以GB2312編碼。

舉例來講,百度是GB2312編碼,Google是UTF-8編碼。所以,從它們的搜索框中搜索同一個詞「春節」,生成的查詢字符串是不同的。

百度生成的是%B4%BA%BD%DA,這是GB2312編碼。

 

Google生成的是%E6%98%A5%E8%8A%82,這是UTF-8編碼。

 

因此,結論3就是,GETPOST方法的編碼,用的是網頁的編碼。

 

參考文獻:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

5、網頁、js編碼

5、狀況4Ajax調用的URL包含漢字

前面三種狀況都是由瀏覽器發出HTTP請求,最後一種狀況則是由Javascript生成HTTP請求,也就是Ajax調用。仍是根據呂瑞麟老師的文章,在這種狀況下,IE和Firefox的處理方式徹底不同。

舉例來講,有這樣兩行代碼:

  url = url + "?q=" +document.myform.elements[0].value; // 假定用戶在表單中提交的值是「春節」這兩個字

  http_request.open('GET', url, true);

那麼,不管網頁使用什麼字符集,IE傳送給服務器的老是「q=%B4%BA%BD%DA」,而Firefox傳送給服務器的老是「q=%E6%98%A5%E8%8A%82」。也就是說,Ajax調用中,IE老是採用GB2312編碼(操做系統的默認編碼),而Firefox老是採用utf-8編碼。這就是咱們的結論4

6、Javascript函數:escape()

好了,到此爲止,四種狀況都說完了。

假定前面你都看懂了,那麼此時你應該會感到很頭痛。由於,實在太混亂了。不一樣的操做系統、不一樣的瀏覽器、不一樣的網頁字符集,將致使徹底不一樣的編碼結果。若是程序員要把每一種結果都考慮進去,是否是太恐怖了?有沒有辦法,可以保證客戶端只用一種編碼方法向服務器發出請求?

回答是有的,就是使用Javascript先對URL編碼,而後再向服務器提交,不要給瀏覽器插手的機會。由於Javascript的輸出老是一致的,因此就保證了服務器獲得的數據是格式統一的。

Javascript語言用於編碼的函數,一共有三個,最古老的一個就是escape()。雖然這個函數如今已經不提倡使用了,可是因爲歷史緣由,不少地方還在使用它,因此有必要先從它講起。

實際上,escape()不能直接用於URL編碼,它的真正做用是返回一個字符的Unicode編碼值。好比「春節」的返回結果是%u6625%u8282,也就是說在Unicode字符集中,「春」是第6625個(十六進制)字符,「節」是第8282個(十六進制)字符。

 

它的具體規則是,除了ASCII字母、數字、標點符號「@ * _ + - . /」之外,對其餘全部字符進行編碼。在\u0000到\u00ff之間的符號被轉成%xx的形式,其他符號被轉成%uxxxx的形式。對應的解碼函數是unescape()。

因此,「Hello World」的escape()編碼就是「Hello%20World」。由於空格的Unicode值是20(十六進制)。

 

還有兩個地方須要注意。

首先,不管網頁的原始編碼是什麼,一旦被Javascript編碼,就都變爲unicode字符。也就是說,Javascipt函數的輸入和輸出,默認都是Unicode字符。這一點對下面兩個函數也適用。

 

其次,escape()不對「+」編碼。可是咱們知道,網頁在提交表單的時候,若是有空格,則會被轉化爲+字符。服務器處理數據的時候,會把+號處理成空格。因此,使用的時候要當心。

7、Javascript函數:encodeURI()

encodeURI()是Javascript中真正用來對URL編碼的函數。

它着眼於對整個URL進行編碼,所以除了常見的符號之外,對其餘一些在網址中有特殊含義的符號「; / ? : @ & = + $ , #」,也不進行編碼。編碼後,它輸出符號的utf-8形式,而且在每一個字節前加上%。

 

它對應的解碼函數是decodeURI()。

 

須要注意的是,它不對單引號'編碼。

8、Javascript函數:encodeURIComponent()

最後一個Javascript編碼函數是encodeURIComponent()。與encodeURI()的區別是,它用於對URL的組成部分進行個別編碼,而不用於對整個URL進行編碼。

所以,「; / ? : @ & = + $ , #」,這些在encodeURI()中不被編碼的符號,在encodeURIComponent()中通通會被編碼。至於具體的編碼方法,二者是同樣。

 

它對應的解碼函數是decodeURIComponent()。

 

參考文獻:http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

6、tomcat容器的編碼

   一、啓動tomcat容器時的編碼

    在第二節中,說道:慎用getBytes(),他是基於平臺的,tomcat中JVM啓動的時候file.encoding這個屬性值究竟是什麼,由誰決定?

       Linux修改catalina.sh文件

JAVA_OPTS=」-server -Dfile.encoding=GBK -Xms=512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m -verbose:gc -Xloggc:${CATALINA_HOME}/logs/gc.log`date +%Y-%m-%d-%H-%M` -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -noclassgc」

      

    若是你像上面這樣告訴JVM,啓動我tomcat容器的時候,使用GBK來編碼,後續的getBytes返回的就是GBK編碼的東西,這點特別在部署的tomcat的時候注意。

 

   二、tomcat對錶單中get提交的數據,出亂碼看哪裏?

    get提交的tomcat從4.x以後不是使用request.setCharacterEncoding("字符集"),從這裏獲取,它直接使用直接對參數進行編碼,這個編碼他是從tomcat的配置文件中獲取<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> 

   

   三、tomcat對錶單中post提交的數據,出亂碼看哪裏?

    對於post須要在web.xml裏面配置攔截器,主要是在程序第一次從request裏面獲取參數以前把request給設定了具體的編碼

    publicvoid doFilter(ServletRequest req, ServletResponse resp,FilterChain chain)throws IOException, ServletException {

           req.setCharacterEncoding(encoding);//這裏是過濾器,直接設置編碼

           HttpServletRequest request = (HttpServletRequest)req;

           HttpServletResponse response = (HttpServletResponse)resp;

           ActionContext.setContext(request, response);

          

           chain.doFilter(req,resp);

           ActionContext.removeContext();

    }

 還有一點須要注意的是:你在web.xml裏面配置過濾器的優先級是否是最高,必定要在request出現以前攔截。

   四、tomcat對jsp頁面的編碼,看哪裏?

    你還記得jsp頁面中有下面這句話麼?pageEncoding="utf-8"告訴tomcat,你編譯我jsp文件的時候使用他來編譯,若是這句話沒有,他默認使用charset=utf-8"這句話來編譯,同時注意保存jsp的時候使用編碼也應該與pageEncoding="utf-8"保持一致

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

 

   你還記得下面這句話麼?由他來告訴瀏覽器使用什麼編碼的

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

7、mysql數據庫的編碼

一、查看數據庫編碼

須要以root用戶身份登錄才能夠查看數據庫編碼方式(以root用戶身份登錄的命令爲:

>mysql -u root –p,以後兩次輸入root用戶的密碼),查看數據庫的編碼方式命令爲:

 >show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

從以上信息可知數據庫的編碼爲latin1,須要修改成gbk或者是utf8;

其中,character_set_client爲客戶端編碼方式;character_set_connection爲創建鏈接使用的編碼;character_set_database數據庫的編碼;

character_set_results結果集的編碼;

character_set_server數據庫服務器的編碼;

只要保證以上四個採用的編碼方式同樣,就不會出現亂碼問題。

 

二、更改數據庫編碼

中止MySQL的運行
/etc/init.d/mysql start (stop) 爲啓動和中止服務器

MySQL主配置文件爲my.cnf,通常目錄爲/etc/mysql

var/lib/mysql/ 放置的是數據庫表文件夾,這裏的mysql至關於windows下mysql的date文件夾

當咱們須要修改MySQL數據庫的默認編碼時,須要編輯my.cnf文件進行編碼修改,在linux下修改mysql的配置文件my.cnf,文件位置默認/etc/my.cnf文件

找到客戶端配置[client] 在下面添加
default-character-set=utf8 默認字符集爲utf8
在找到[mysqld] 添加
default-character-set=utf8 默認字符集爲utf8
init_connect='SET NAMES utf8' (設定鏈接mysql數據庫時使用utf8編碼,以讓mysql數據庫爲utf8運行)

 

三、改了以後,爲何win裏面cmd中的那個console控制檯仍是顯示亂碼了?

         Mysql經過客戶端發送到控制檯展現以前是utf8,可是console是使用系統的默認編碼(gbk),因此在中文的時候發生了亂碼顯示,可是不影響程序操做,只是顯示的時候有問題。

         如何解決了?

         方法一:(只對當前窗口有用,推薦使用)

         輸入:set names gbk

 

 

         方法二:將控制檯展現編碼改爲utf8(對全部的窗口,不推薦)

一、打開CMD.exe命令行窗口 二、經過 chcp命令改變代碼頁,UTF-8的代碼頁爲65001 F:\trash>chcp 65001執行該操做後,代碼頁就被變成UTF-8了。可是,在窗口中仍舊不能正確顯示UTF-8字符。 三、修改窗口屬性,改變字體 在命令行標題欄上點擊右鍵,選擇"屬性"->"字體",將字體修改成True Type字體"Lucida Console",而後點擊肯定將屬性應用到當前窗口。 四、經過以上操做並不能徹底解決問題,由於顯示出來的內容有可能不徹底。能夠先最小化,而後最大化命令行窗口,文件的內容就完整的顯示出來了。

相關文章
相關標籤/搜索