????作為一名程序員,肯定有被亂碼困擾的時候,真到了百思不得其解的時候,就會覺得:英文程序員真幸福。但其實只要明白編碼之間的轉換規律,其實亂碼還是很好解決的。我們都知道字符串在保存和傳輸的時候需要先經過編碼成二進制,到達目的地后再進行解碼。
所以亂碼的本質就是:讀取二進制的時候采用的編碼和最初將字符轉換成二進制時的編碼不一致。
?ps:編碼有動詞含義也有名詞含義,名詞含義就是一套字符和二進制序列之間的轉換規則,動詞含義是使用這種規則將字符轉換成二進制序列。
比如我們通常使用下面代碼處理get請求中的中文亂碼,
String?s1?=?new?String(s.getBytes("iso8859-1"),?"utf-8");
這是由于編碼的時候使用的是utf-8而解碼的時候使用的是iso8859-1那么當然會出現亂碼,這時將得到的字符串使用iso8859-1編碼就可以得到原始編碼,然后再使用utf-8進行解碼就能得到正確的字符。
再看一段代碼,
public?class?EncodingTest?{public?static?void?main(String[]?args)?throws?UnsupportedEncodingException?{String?srcString?=?"我們是中國人";String?utf2GbkString?=?new?String(srcString.getBytes("UTF-8"),"GBK");System.out.println("UTF-8轉換成GBK:"+utf2GbkString);String?utf2Gbk2UtfString?=?new?String(utf2GbkString.getBytes("GBK"),"UTF-8");System.out.println("UTF-8轉換成GBK再轉成UTF-8:"+utf2Gbk2UtfString);}
}
因為UTF-8和GBK是兩套中文支持較好的編碼,所以經常會進行它們之間的轉換,這里就以它們舉例。
以上代碼運行打印出以下內容:
UTF-8轉換成GBK:鎴戜滑鏄腑鍥戒漢
UTF-8轉換成GBK再轉成UTF-8:我們是中國人
我們看到,將"我們是中國人"以UTF-8編碼轉換成byte數組(byte數組其實就相當于二進制序列了,此過程即編碼),再以GBK編碼和byte數組創建新的字符串(此過程即以GBK編碼去解碼byte數組,得到字符串),就產生亂碼了。
因為編碼采用的UTF-8和解碼采用的GBk不是同一種編碼,所以最后結果亂碼了。
之后再對亂碼使用GBK編碼,還原到解碼前的byte數組,再使用和最初編碼時使用的一致的編碼UTF-8進行解碼,就可得到最初的“我們是中國人”。這個例子和上邊get請求產生亂碼類似。
有時候這種編碼轉換并不會奏效,看下面代碼
public?class?EncodingTest?{??public?static?void?main(String[]?args)?throws?UnsupportedEncodingException?{??String?srcString?=?"我們是中國人";??String?gbk2UtfString?=?new?String(srcString.getBytes("GBK"),?"UTF-8");??System.out.println("GBK轉換成UTF-8:"?+?gbk2UtfString);??String?gbk2Utf2GbkString?=?new?String(gbk2UtfString.getBytes("UTF-8"),?"GBK");??System.out.println("GBK轉換成UTF-8再轉成GBK:"?+?gbk2Utf2GbkString);??}??
}
這次我們反過來,先將字符串以GBK編碼再以UTF-8解碼,再以UTF-8編碼,再以GBK解碼。
這次的運行結果是:
GBK轉換成UTF-8:й
GBK轉換成UTF-8再轉成GBK:錕斤拷錕斤拷錕斤拷錕叫癸拷錕斤拷
萬惡的“錕斤拷”,相信不少人都見過。這里GBK轉成UTF-8亂碼好理解,但是再轉回來怎么變成了“錕斤拷錕斤拷錕斤拷錕叫癸拷錕斤拷”,這似乎不科學。
這其實和UTF-8獨特的編碼方式有關,由于UTF-8需要對unicode字符進行編碼,unicode字符集是一個幾乎支持所有字符的字符集,為了表示這么龐大的字符集,UTF-8可能需要更多的二進制位來表示一個字符,同時為了不致是UTF-8編碼太占存儲空間,根據二八定律,UTF-8采用了一種可變長的編碼方式,即將常用的字符編碼編碼成較短的序列,而不常用的字符用較長的序列表示,這樣讓編碼占用更少的存儲空間的同時也保證了對龐大字符集的支持。
正式由于UTF-8采用的這種特別的變長編碼方式,這一點和其他的編碼很不一樣。比如GBK固定用兩個字節來表示漢字,一個字節來表示英文和其他符號。而utf-8采用的是一種變長的編碼方案從一個字節到四個字節不等,而上文中的中文是使用的三個字節代表一個字符。
UTF-8編碼的讀取方式也比較不同,需要先讀取第一個字節,然后根據這個字節的值才能判斷這個字節所表示的字符共需要多少字節來表示。
對于某一個字符的UTF-8編碼,如果只有一個字節則其最高二進制位為0;如果是多字節,其第一個字節從最高位開始,連續的二進制位值為1的個數決定了其編碼的位數,其余各字節均以10開頭。UTF-8最多可用到6個字節。?
所以當我們將由GBK編碼的12個字節試圖用UTF-8解碼時會出現錯誤,由于GBK編碼出了不可能出現在UTF-8編碼中出現的序列,所以當我們試圖用UTF-8去解碼時,經常會遇到這種不可能序列,對于這種不可能序列,UTF-8把它們轉換成某種不可言喻的字符“”,當這種不可言喻的字符再次以UTF-8進行編碼時,他們已經無法回到最初的樣子了,因為那些是UTF-8編碼不可能編出的序列。然后這個神秘字符再轉換成GBK編碼時就變成了“錕斤拷”。當然,還有很多其他的巧合,可能正好碰到UTF-8中存在的序列,甚至原本不是一個字符的字節,可能是某個字的第二個字節和下一個字的兩個字節,正好被識別成一個UTF-8序列,于是解碼出一個漢字,當然這些在我們看來都是亂碼了,只不過不是“錕斤拷”的樣子。因為不可能序列更普遍存在,所以GBK轉UTF-8再轉GBK時,最常見的便是“錕斤拷”!
所以:以非UTF-8編碼編碼出的字節數組,一旦以UTF-8進行解碼,通常這是一條不歸路,再嘗試將解碼出的字符以UTF-8進行編碼,也無法還原之前的字節數組。相反地,其他的固定長度編碼幾乎都可以順利還原。
轉載于:https://blog.51cto.com/a4boy/1889996