咱們最初學習計算機的時候,都學過ASCII編碼。html
可是爲了表示各類各樣的語言,在計算機技術的發展過程當中,逐漸出現了不少不一樣標準的編碼格式,java
重要的有Unicode、UTF、ISO-8859-1和中國人常用的GB23十二、BIG五、GBK等。mysql
最先的編碼是iso8859-1,和ascii編碼類似。但爲了方便表示各類各樣的語言,逐漸出現了不少標準編碼,重要的有以下幾個。linux
屬於單字節編碼,最多能表示的字符範圍是0-255,應用於英文系列。好比,字母a的編碼爲0x61=97。 web
很明顯,iso8859-1編碼表示的字符範圍很窄,沒法表示中文字符。可是,因爲是單字節編碼,和計算機最基礎的表示單位一致,因此不少時候,仍舊使用iso8859-1編碼來表示。sql
並且在不少協議上,默認使用該編碼。好比,雖然"中文"兩個字不存在iso8859-1編碼,數據庫
以gb2312編碼爲例,應該是"d6d0 cec4"兩個字符(java字符佔2個字節),apache
使用iso8859-1編碼的時候則將它拆開爲4個字節來表示:"d6 d0 ce c4"(事實上,在進行存儲的時候,也是以字節爲單位處理的)。windows
而若是是UTF編碼,則是6個字節"e4 b8 ad e6 96 87"。數組
很明顯,這種表示方法還須要以另外一種編碼爲基礎。
這就是漢字的國標碼,專門用來表示漢字,是雙字節編碼,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。
其中gbk編碼可以用來同時表示繁體字和簡體字,
而gb2312只能表示簡體字,gbk是兼容gb2312編碼的。
這是最統一的編碼,能夠用來表示全部語言的字符,並且是定長雙字節(也有四字節的)編碼,包括英文字母在內。因此能夠說它是不兼容iso8859-1編碼的,也不兼容任何編碼。不過,相對於iso8859-1編碼來講,uniocode編碼只是在前面增長了一個0字節,好比字母a爲"00 61"。
須要說明的是,定長編碼便於計算機處理(注意GB2312/GBK不是定長編碼),而unicode又能夠用來表示全部字符,因此在不少軟件內部是使用unicode編碼來處理的,好比java。
考慮到unicode編碼不兼容iso8859-1編碼,並且容易佔用更多的空間:由於對於英文字母,unicode也須要兩個字節來表示。因此unicode不便於傳輸和存儲。所以而產生了utf編碼,utf編碼兼容iso8859-1編碼,同時也能夠用來表示全部語言的字符,不過,utf編碼是不定長編碼,每個字符的長度從1-6個字節不等。另外,utf編碼自帶簡單的校驗功能。通常來說,英文字母都是用一個字節表示,而漢字使用三個字節。
注意,雖說utf是爲了使用更少的空間而使用的,但那只是相對於unicode編碼來講,若是已經知道是漢字,則使用GB2312/GBK無疑是最節省的。不過另外一方面,值得說明的是,雖然utf編碼對漢字使用3個字節,但即便對於漢字網頁,utf編碼也會比unicode編碼節省,由於網頁中包含了不少的英文字符。
將以"中文"兩個字爲例,經查表能夠知道其
GB2312編碼是"d6d0 cec4",
Unicode編碼爲"4e2d 6587",
UTF編碼就是"e4b8ad e69687"。
注意,這兩個字沒有iso8859-1編碼,但能夠用iso8859-1編碼來表示。
下面以對中文字符串"a中文"的編碼轉換爲例,來了解各類編碼之間的轉換
1)Unicode和GBK
測試結果以下,每一個漢字轉換爲兩個字節,且是可逆的,即經過字節能夠轉換回字符串
String-GBK〉ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4
ByteArray-GBK〉String:0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u4E2D/u6587(a中文)
2)Unicode和UTF-8
測試結果以下,每一個漢字轉換爲三個字節,且是可逆的,即經過字節能夠轉換回字符串
String-UTF-8〉ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87
ByteArray-UTF-8〉String:0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u4E2D/u6587(a中文)
3)Unicode和ISO-8859-1
測試結果以下,當存在漢字時轉換失敗,非可逆,即經過字節不能再轉換回字符串
String-ISO-8859-1〉ByteArray:/u0061/u4E2D/u6587(a中文)-〉0x61 0x3F 0x3F
ByteArray-ISO-8859-1〉String:0x61 0x3F 0x3F-〉/u0061/u003F/u003F(a??)
在上面直接轉換中,由字符串(Unicode)生成的字節數組,在構造回字符串時,使用的是正確的編碼集合,若是使用的不是正確的編碼集合會怎樣呢?會正確構造嗎?若是不能正確構造能有辦法恢復嗎?會信息丟失嗎?
下面咱們就來看看這種狀況,這部分能夠說明在某些狀況下雖然咱們最終正確顯示告終果,但其間仍然進行了不正確的轉換。
1)可以正確顯示的中間不正確轉換
咱們知道String-GBK〉ByteArray-GBK〉String是正確的,但若是咱們採用String-GBK〉
ByteArray-ISO-8859-1〉String呢?經過測試結果以下:
String-GBK〉ByteArray-ISO-8859-1〉String:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u00D6/u00D0/u00CE/u00C4(a????
這時咱們獲得的字符串爲?亂碼「a????」,可是經過繼續轉換咱們仍然能夠復原回正確的字符串「a中文」,過程以下:
對應:/u0061/u4E2D/u6587(a中文)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u00D6/u00D0/u00CE/u00C4(a????)-〉0x61 0xD6 0xD0 0xCE 0xC4-〉/u0061/u4E2D/u6587(a中文)
也就是咱們在首次構造字符串時,咱們用了錯誤的編碼集合獲得了錯誤的亂碼,可是咱們經過錯上加錯,再用錯誤的編碼集合獲取字節數組,而後再用正確的編碼集合構造,就 又恢復了正確的字符串。這時就屬因而「可以正確顯示的中間不正確轉換」。在Jsp頁面提交數據處理時經常發生這種狀況。
此外可以正確顯示的中間不正確轉換還有:
String-UTF-8〉ByteArray-GBK〉String-GBK〉ByteArray-UTF-8〉String
對應:/u0061/u4E2D/u6587(a中文)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u6D93/uE15F/u6783(a涓枃)-〉0x61 0xE4 0xB8 0xAD 0xE6%0x96 0x87-〉/u0061/u4E2D/u6587(a中文)
1)一個漢字對應一個問號
在經過ISO-8859-1從字符串獲取字節數組時,因爲一個Unicode轉換成一個byte,當遇到不認識的Unicode時,轉換爲0x3F,這樣不管用哪一種編碼構造時都會產生一個?亂碼。
2)一個漢字對應兩個問號
在經過GBK從字符串獲取字節數組時,因爲一個Unicode轉換成兩個byte,若是此時用ISO-8859-1或用UTF-8構造字符串就會出現兩個問號。
如果經過ISO-8859-1構造能夠再經過上面所說的錯上加錯恢復(即再經過從ISO-8859-1解析,用GBK構造);
如果經過UTF-8構造則會產生Unicode字符"/uFFFD",不能恢復,若再經過String-UTF-8〉ByteArray-GBK〉String,則會出現雜碼,如a錕斤拷錕斤拷
3)一個漢字對應三個問號
在經過UTF-8從字符串獲取字節數組時,因爲一個
這是java字符串處理的一個標準函數,其做用是將字符串所表示的字符按照charset編碼,並以字節方式表示。注意字符串在java內存中老是按unicode編碼存儲的。好比"中文",正常狀況下(即沒有錯誤的時候)存儲爲"4e2d 6587",若是charset爲"gbk",則被編碼爲"d6d0 cec4",而後返回字節"d6 d0 ce c4".若是charset爲"utf8"則最後是"e4 b8 ad e6 96 87".若是是"iso8859-1",則因爲沒法編碼,最後返回 "3f 3f"(兩個問號)。
在java應用軟件中,會有多處涉及到字符集編碼,有些地方須要進行正確的設置,有些地方須要進行必定程度的處理。
這是java字符串處理的一個標準函數,其做用是將字符串所表示的字符按照charset編碼,並以字節方式表示。
注意字符串在java內存中老是按unicode編碼存儲的。
好比"中文",正常狀況下(即沒有錯誤的時候)存儲爲"4e2d 6587",
若是charset爲"gbk",則被編碼爲"d6d0 cec4",而後返回字節"d6 d0 ce c4"。
若是charset爲"utf8"則最後是"e4 b8 ad e6 96 87"。
若是是"iso8859-1",則因爲沒法編碼,最後返回 "3f 3f"(兩個問號)。
這是java字符串處理的另外一個標準函數,和上一個函數的做用相反,將字節數組按照charset編碼進行組合識別,最後轉換爲unicode存儲。
參考上述getBytes的例子,"gbk" 和"utf8"均可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了"003f 003f"(兩個問號)。
由於utf8能夠用來表示/編碼全部字符,因此new String( str.getBytes( "utf8" ), "utf8" ) === str,即徹底可逆。
該函數用來設置http請求或者相應的編碼。
對於request,是指提交內容的編碼,指定後能夠經過getParameter()則直接得到正確的字符串,若是不指定,則默認使用iso8859-1編碼,須要進一步處理。
參見下述"表單輸入"。值得注意的是在執行setCharacterEncoding()以前,不能執行任何getParameter()。
java doc上說明:This method must be called prior to reading request parameters or reading input using getReader()。
該指定只對POST方法有效,對GET方法無效。分析緣由:
POST方法在執行第一個getParameter()的時候,java將會按照編碼分析全部的提交內容,然後續的getParameter()再也不進行分析,因此setCharacterEncoding()無效。
GET方法提交表單是,提交的內容在URL中,一開始就已經按照編碼分析全部的提交內容,setCharacterEncoding()天然就無效。
注意:iso-8859-1是JAVA網絡傳輸使用的標準字符集,而gb2312是標準中文字符集,當你做出提交表單等須要網絡傳輸的操做的時候,就須要把 iso-8859-1轉換爲gb2312字符集顯示,不然若是按瀏覽器的gb2312格式來解釋iso-8859-1字符集的話,因爲2者不兼容,因此會 是亂碼.
//1)將字符串用指定的編碼集合解析成字節數組,完成Unicode-〉//charsetName轉換 public byte[] getBytes(String charsetName) throws UnsupportedEncodingException //2)將字節數組以指定的編碼集合構形成字符串,完成charsetName-〉Unicode轉換 public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
String s = "你好"; // 編碼 byte[] utf = s.getBytes("utf-8"); byte[] gbk = s.getBytes("gbk"); System.out.println("utf-8編碼:" + Arrays.toString(utf));//[-28,-67,-96,-27,-91,-67] 6個字節 System.out.println("gbk編碼:" + Arrays.toString(gbk));//[-60,-29,-70,-61] 4個字節 // 解碼 String s1 = new String(utf, "utf-8"); // 你好 String s2 = new String(utf, "gbk"); // gbk解碼:浣犲ソ gbk用2個字節解碼,因此會多一個字符 String s3 = new String(gbk, "utf-8"); // gbk用utf-8解碼:??? utf-8解碼須要6個字節 System.out.println("--------------------"); System.out.println("utf-8解碼:" + s1); System.out.println("gbk解碼:" + s2); System.out.println("gbk用utf-8解碼:" + s3); System.out.println("---------------------"); System.out.println("用utf-8編碼回去"); s3 = new String(s3.getBytes("utf-8"), "gbk"); // 錕斤拷錕? gbk用utf-8解碼後沒法編回去 System.out.println(s3);
規律:
utf-8編碼能夠用gbk和iso8859-1解碼後編回去
gbk編碼後只能用iso8859-1解碼後編回去
下面分析兩個有表明性的例子,說明java對編碼有關問題的處理方法。
User input *(gbk:d6d0 cec4)
browser *(gbk:d6d0 cec4)
web server iso8859-1(00d6 00d 000ce 00c4) class,
須要在class中進行處理:
getbytes("iso8859-1")爲d6 d0 ce c4,
new String("gbk")爲d6d0 cec4,
內存中以unicode編碼則爲4e2d 6587。
l 用戶輸入的編碼方式和頁面指定的編碼有關,也和用戶的操做系統有關,因此是不肯定的,上例以gbk爲例。
l 從browser到web server,能夠在表單中指定提交內容時使用的字符集,不然會使用頁面指定的編碼。而若是在url中直接用?的方式輸入參數,則其編碼每每是操做系統自己的編碼,由於這時和頁面無關。上述仍舊以gbk編碼爲例。
l Web server接收到的是字節流,默認時(getParameter)會以iso8859-1編碼處理之,結果是不正確的,因此須要進行處理。但若是預先設置了編碼(經過request. setCharacterEncoding ()),則可以直接獲取到正確的結果。
l 在頁面中指定編碼是個好習慣,不然可能失去控制,沒法指定正確的編碼。
假設文件是gbk編碼保存的,而編譯有兩種編碼選擇:gbk或者iso8859-1,前者是中文windows的默認編碼,後者是Linux的默認編碼,固然也能夠在編譯時指定編碼。
Jsp *(gbk:d6d0 cec4)
java file *(gbk:d6d0 cec4)
compiler read uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)
compiler write utf(gbk: e4b8ad e69687; iso8859-1: *)
compiled file unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4) class。
因此用gbk編碼保存,而用iso8859-1編譯的結果是不正確的。
class unicode(4e2d 6587) system.out / jsp.out gbk(d6d0 cec4) os console / browser。
l 文件能夠以多種編碼方式保存,中文windows下,默認爲ansi/gbk。
l 編譯器讀取文件時,須要獲得文件的編碼,若是未指定,則使用系統默認編碼。通常class文件,是以系統默認編碼保存的,因此編譯不會出問題,但對於jsp文件,若是在中文windows下編輯保存,而部署在英文linux下運行/編譯,則會出現問題。因此須要在jsp文件中用pageEncoding指定編碼。
l Java編譯的時候會轉換成統一的unicode編碼處理,最後保存的時候再轉換爲utf編碼。
l 當系統輸出字符的時候,會按指定編碼輸出,對於中文windows下,System.out將使用gbk編碼,而對於response(瀏覽器),則使用jsp文件頭指定的contentType,或者能夠直接爲response指定編碼。同時,會告訴browser網頁的編碼。若是未指定,則會使用iso8859-1編碼。對於中文,應該爲browser指定輸出字符串的編碼。
l browser顯示網頁的時候,首先使用response中指定的編碼(jsp文件頭指定的contentType最終也反映在response上),若是未指定,則會使用網頁中meta項指定中的contentType。
對於web應用程序,和編碼有關的設置或者函數以下。
指定文件的存儲編碼,很明顯,該設置應該置於文件的開頭。例如:<%@page pageEncoding="GBK"%>。另外,對於通常class文件,能夠在編譯的時候指定編碼。
指定文件輸出到browser時使用的編碼,該設置也應該置於文件的開頭。例如:<%@ page contentType="text/html; charset= GBK" %>。該設置和response.setCharacterEncoding("GBK")等效。
指定網頁使用的編碼,該設置對靜態網頁尤爲有做用。由於靜態網頁沒法採用jsp的設置,並且也沒法執行response.setCharacterEncoding()。例如: <META http-equiv="Content-Type" content="text/html; charset=GBK" />
若是同時採用了jsp輸出和meta設置兩種編碼指定方式,則jsp指定的優先。由於jsp指定的直接體如今response中。
須要注意的是,apache有一個設置能夠給無編碼指定的網頁指定編碼,該指定等同於jsp的編碼指定方式,因此會覆蓋靜態網頁中的meta指定。因此有人建議關閉該設置。
當瀏覽器提交表單的時候,能夠指定相應的編碼。例如:<form accept-charset= "gb2312">。通常沒必要不使用該設置,瀏覽器會直接使用網頁的編碼。
在JSP頁面獲取表單的值時會出現亂碼,有兩種解決方法:
1.post:在調用getParameter以前經過request.setCharacterEncoding設置字符編碼
2.get:調用new String(str.getBytes("iso8859-1"), "UTF-8");編碼後解碼
下面討論幾個相關的系統軟件。
很明顯,要支持多語言,應該將數據庫的編碼設置成utf或者unicode,而utf更適合與存儲。可是,若是中文數據中包含的英文字母不多,其實unicode更爲適合。
數據庫的編碼能夠經過mysql的配置文件設置,例如default-character-set=utf8。
還能夠在數據庫連接URL中設置,例如: useUnicode=true&characterEncoding=UTF-8。
注意這二者應該保持一致,在新的sql版本里,在數據庫連接URL裏能夠不進行設置,但也不能是錯誤的設置。
Oracle中字符集編碼決定
select userenv('language') from dual;
SIMPLIFIED CHINESE_CHINA.ZHS16GBK
SIMPLIFIED CHINESE_CHINA.AL32UTF8
select lengthb('你') from dual;
appache和編碼有關的配置在httpd.conf中,例如AddDefaultCharset UTF-8。如前所述,該功能會將全部靜態頁面的編碼設置爲UTF-8,最好關閉該功能。
另外,apache還有單獨的模塊來處理網頁響應頭,其中也可能對編碼進行設置。
這裏所說的linux默認編碼,是指運行時的環境變量。兩個重要的環境變量是LC_ALL和LANG,默認編碼會影響到java URLEncode的行爲,下面有描述。
建議都設置爲"zh_CN.UTF-8"。
爲了支持中文文件名,linux在加載磁盤時應該指定字符集,例如:mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。
另外,如前所述,使用GET方法提交的信息不支持request.setCharacterEncoding(),但能夠經過tomcat的配置文件指定字符集,在tomcat的server.xml文件中,形如:<Connector ... URIEncoding="GBK"/>。這種方法將統一設置全部請求,而不能針對具體頁面進行設置,也不必定和browser使用的編碼相同,因此有時候並非所指望的。
URL地址中含有中文字符是很麻煩的,前面描述過使用GET方法提交表單的狀況,使用GET方法時,參數就是包含在URL中。
對於URL中的一些特殊字符,瀏覽器會自動進行編碼。這些字符除了"/?&"等外,還包括unicode字符,好比漢字。這時的編碼比較特殊。
IE有一個選項"老是使用UTF-8發送URL",
當該選項有效時,IE將會對特殊字符進行UTF-8編碼,同時進行URL編碼。
若是該選項無效,則使用默認編碼"GBK",而且不進行URL編碼。可是,對於URL後面的參數,則老是不進行編碼,至關於UTF-8選項無效。
好比"中文.html?a=中文",
當UTF-8選項有效時,將發送連接"%e4%b8%ad%e6%96%87.html?a=/x4e/x2d/x65/x87";
而UTF-8選項無效時,將發送連接"/x4e/x2d/x65/x87.html?a=/x4e/x2d/x65/x87"。
注意後者前面的"中文"兩個字只有4個字節,而前者卻有18個字節,這主要時URL編碼的緣由。
當web server(tomcat)接收到該連接時,將會進行URL解碼,即去掉"%",同時按照ISO8859-1編碼(上面已經描述,可使用URLEncoding來設置成其它編碼)識別。
上述例子的結果分別是"/ue4/ub8/uad/ue6/u96/u87.html?a=/u4e/u2d/u65/u87"和"/u4e/u2d/u65/u87.html?a=/u4e/u2d/u65/u87",
注意前者前面的"中文"兩個字恢復成了6個字符。這裏用"/u",表示是unicode。
因此,因爲客戶端設置的不一樣,相同的連接,在服務器上獲得了不一樣結果。這個問題很多人都遇到,卻沒有很好的解決辦法。因此有的網站會建議用戶嘗試關閉UTF-8選項。不過,下面會描述一個更好的處理辦法。
熟悉的人都知道,apache有一個功能強大的rewrite模塊,這裏不描述其功能。須要說明的是該模塊會自動將URL解碼(去除%),即完成上述web server(tomcat)的部分功能。有相關文檔介紹說可使用[NE]參數來關閉該功能,但我試驗並未成功,多是由於版本(我使用的是apache 2.0.54)問題。另外,當參數中含有"?& "等符號的時候,該功能將致使系統得不到正常結果。
rewrite自己彷佛徹底是採用字節處理的方式,而不考慮字符串的編碼,因此不會帶來編碼問題。
這是Java自己提供對的URL編碼函數,完成的工做和上述UTF-8選項有效時瀏覽器所作的工做類似。值得說明的是,java已經不同意不指定編碼來使用該方法(deprecated)。應該在使用的時候增長編碼指定。
當不指定編碼的時候,該方法使用系統默認編碼,這會致使軟件運行結果得不肯定。好比對於"中文",當系統默認編碼爲"gb2312"時,結果是"%4e%2d%65%87",而默認編碼爲"UTF-8",結果倒是"%e4%b8%ad%e6%96%87",後續程序將難以處理。
另外,這兒說的系統默認編碼是由運行tomcat時的環境變量LC_ALL和LANG等決定的,曾經出現過tomcat重啓後就出現亂碼的問題,最後才鬱悶的發現是由於修改修改了這兩個環境變量。
建議統一指定爲"UTF-8"編碼,可能須要修改相應的程序。
下面描述一些和編碼有關的其餘問題。
除了瀏覽器和控制檯與編碼有關外,一些客戶端也頗有關係。好比在使用SecureCRT鏈接linux時,應該讓SecureCRT的顯示編碼(不一樣的session,能夠有不一樣的編碼設置)和linux的編碼環境變量保持一致。不然看到的一些幫助信息,就多是亂碼。
另外,mysql有本身的編碼設置,也應該保持和SecureCRT的顯示編碼一致。不然經過SecureCRT執行sql語句的時候,可能沒法處理中文字符,查詢結果也會出現亂碼。
對於Utf-8文件,不少編輯器(好比記事本)會在文件開頭增長三個不可見的標誌字節,若是做爲mysql的輸入文件,則必需要去掉這三個字符。(用linux的vi保存能夠去掉這三個字符)。一個有趣的現象是,在中文windows下,建立一個新txt文件,用記事本打開,輸入"連通"兩個字,保存,再打開,你會發現兩個字沒了,只留下一個小黑點。
若是須要統一設置編碼,則經過filter進行設置是個不錯的選擇。在filter class中,能夠統一爲須要的請求或者回應設置編碼。參加上述setCharacterEncoding()。這個類apache已經給出了能夠直接使用的例子SetCharacterEncodingFilter。
很明顯,以POST提交信息時,URL有更好的可讀性,並且能夠方便的使用setCharacterEncoding()來處理字符集問題。
但GET方法造成的URL可以更容易表達網頁的實際內容,也可以用於收藏。
從統一的角度考慮問題,建議採用GET方法,這要求在程序中得到參數是進行特殊處理,而沒法使用setCharacterEncoding()的便利,若是不考慮rewrite,就不存在IE的UTF-8問題,能夠考慮經過設置URIEncoding來方便獲取URL中的參數。
GBK同時包含簡體和繁體編碼,也就是說同一個字,因爲編碼不一樣,在GBK編碼下屬於兩個字。有時候,爲了正確取得完整的結果,應該將繁體和簡體進行統一。能夠考慮將UTF、GBK中的全部繁體字,轉換爲相應的簡體字,BIG5編碼的數據,也應該轉化成相應的簡體字。固然,仍舊以UTF編碼存儲。
例如,對於"語言 語言",
用UTF表示爲"/xE8/xAF/xAD/xE8/xA8/x80 /xE8/xAA/x9E/xE8/xA8/x80",
進行簡繁體編碼轉換後應該是兩個相同的 "/xE8/xAF/xAD/xE8/xA8/x80>"。
參考:http://blog.csdn.net/qinysong/article/details/1179513