UTF-8與GBK互轉(zhuǎn)罢洲,為什么會(huì)亂碼

作為一名程序員硫朦,肯定有被亂碼困擾的時(shí)候,真到了百思不得其解的時(shí)候侵俗,就會(huì)覺得:英文程序員真幸福锨用。

但其實(shí)只要明白編碼之間的轉(zhuǎn)換規(guī)律,其實(shí)亂碼so easy~

我們知道隘谣,計(jì)算機(jī)存儲(chǔ)數(shù)據(jù)都是2進(jìn)制增拥,就是0和1,那么這么多的字符就都需要有自己對(duì)應(yīng)的0和1組成的序列寻歧,計(jì)算機(jī)將需要存儲(chǔ)的字符轉(zhuǎn)換成它們對(duì)應(yīng)的01序列掌栅,然后就可以儲(chǔ)存在電腦里了。

比如我們可以定義用8位2進(jìn)制表示一個(gè)字符熄求,“00000000”表示小寫字母“a”渣玲,“00000001”表示小寫字母“b”逗概,那么計(jì)算機(jī)要存儲(chǔ)“ab”的時(shí)候弟晚,其實(shí)在計(jì)算機(jī)里的存儲(chǔ)的是“0000000000000001”,讀取的時(shí)候先讀取前8位,根據(jù)對(duì)應(yīng)關(guān)系卿城,可以解碼出“a”枚钓,再讀取后8位,又可以解碼出“b”瑟押,這樣就讀出了當(dāng)時(shí)寫入的“ab”了搀捷。而我們定義的這種字符和二進(jìn)制序列的對(duì)應(yīng)關(guān)系,就可以稱之為編碼多望。我們?nèi)绻枰獙ⅰ癮b”發(fā)送給別人嫩舟,因?yàn)榫W(wǎng)絡(luò)也是基于二進(jìn)制,所以只要先約定好編碼規(guī)則怀偷,就可以發(fā)送“0000000000000001”家厌,然后對(duì)方根據(jù)約定的編碼解碼,就可以得到“ab”∽倒ぃ現(xiàn)在是互聯(lián)網(wǎng)的時(shí)代饭于,我們經(jīng)常需要和其他的計(jì)算機(jī)進(jìn)行交互,一套編碼系統(tǒng)還是比較復(fù)雜的维蒙,所以大家就需要約定統(tǒng)一的編碼掰吕,這樣的編碼是大家都約定好的,就不用再去約定編碼規(guī)則了~然而颅痊,為了滿足各種不同的需求殖熟,人們還是制定了很多種編碼,沒有哪一種能全面替代其他編碼斑响,所以現(xiàn)在多種編碼并存吗讶。通常這些編碼都被大家所接受和熟知,所以現(xiàn)在不用再通信前商量編碼的對(duì)應(yīng)規(guī)則和細(xì)節(jié)恋捆,只需要告訴對(duì)方照皆,我采用的是什么通用編碼,彼此就能愉快地通信了沸停。

所以亂碼的本質(zhì)就是:讀取二進(jìn)制的時(shí)候采用的編碼和最初將字符轉(zhuǎn)換成二進(jìn)制時(shí)的編碼不一致膜毁。

ps:編碼有動(dòng)詞含義也有名詞含義,名詞含義就是一套字符和二進(jìn)制序列之間的轉(zhuǎn)換規(guī)則愤钾,動(dòng)詞含義是使用這種規(guī)則將字符轉(zhuǎn)換成二進(jìn)制序列瘟滨。

好了,廢話不多能颁,直接上一段代碼:

import java.io.UnsupportedEncodingException;

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轉(zhuǎn)換成GBK:"+utf2GbkString);

String utf2Gbk2UtfString = new String(utf2GbkString.getBytes("GBK"),"UTF-8");

System.out.println("UTF-8轉(zhuǎn)換成GBK再轉(zhuǎn)成UTF-8:"+utf2Gbk2UtfString);

}

}

因?yàn)閁TF-8和GBK是兩套中文支持較好的編碼杂瘸,所以經(jīng)常會(huì)進(jìn)行它們之間的轉(zhuǎn)換,這里就以它們舉例伙菊。

以上代碼運(yùn)行打印出以下內(nèi)容:

UTF-8轉(zhuǎn)換成GBK:鎴戜滑鏄腑鍥戒漢

UTF-8轉(zhuǎn)換成GBK再轉(zhuǎn)成UTF-8:我們是中國人

我們看到败玉,將"我們是中國人"以UTF-8編碼轉(zhuǎn)換成byte數(shù)組(byte數(shù)組其實(shí)就相當(dāng)于二進(jìn)制序列了敌土,此過程即編碼),再以GBK編碼和byte數(shù)組創(chuàng)建新的字符串(此過程即以GBK編碼去解碼byte數(shù)組运翼,得到字符串)返干,就產(chǎn)生亂碼了。

因?yàn)?b>編碼采用的UTF-8和解碼采用的GBK不是同一種編碼血淌,所以最后結(jié)果亂碼了矩欠。

之后再對(duì)亂碼使用GBK編碼,還原到解碼前的byte數(shù)組悠夯,再使用和最初編碼時(shí)使用的一致的編碼UTF-8進(jìn)行解碼癌淮,就可得到最初的“我們是中國人”。

這種多余的轉(zhuǎn)換有時(shí)候還是很有用的沦补,比如ftp協(xié)議只支持ISO-8859-1編碼该默,這個(gè)時(shí)候如果要傳中文,只能先換成ISO-8859-1的亂碼策彤,ftp完成后栓袖,再轉(zhuǎn)回UTF-8就又可以得到正常的中文了。

怎么樣店诗?編碼轉(zhuǎn)換是不是so easy裹刮?那該來點(diǎn)正經(jīng)的了:

import java.io.UnsupportedEncodingException;

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轉(zhuǎn)換成UTF-8:" + gbk2UtfString);

String gbk2Utf2GbkString = new String(gbk2UtfString.getBytes("UTF-8"), "GBK");

System.out.println("GBK轉(zhuǎn)換成UTF-8再轉(zhuǎn)成GBK:" + gbk2Utf2GbkString);

}

}

這次我們反過來,先將字符串以GBK編碼再以UTF-8解碼庞瘸,再以UTF-8編碼捧弃,再以GBK解碼。

這次的運(yùn)行結(jié)果是:

GBK轉(zhuǎn)換成UTF-8:???????й???

GBK轉(zhuǎn)換成UTF-8再轉(zhuǎn)成GBK:錕斤拷錕斤拷錕斤拷錕叫癸拷錕斤拷

WTF擦囊?违霞?萬惡的“錕斤拷”,相信不少人都見過瞬场。這里GBK轉(zhuǎn)成UTF-8亂碼好理解买鸽,但是再轉(zhuǎn)回來怎么變成了“錕斤拷錕斤拷錕斤拷錕叫癸拷錕斤拷”,這似乎不科學(xué)贯被。

這其實(shí)和UTF-8獨(dú)特的編碼方式有關(guān)眼五,由于UTF-8需要對(duì)unicode字符進(jìn)行編碼,unicode字符集是一個(gè)幾乎支持所有字符的字符集彤灶,為了表示這么龐大的字符集看幼,UTF-8可能需要更多的二進(jìn)制位來表示一個(gè)字符,同時(shí)為了不致使UTF-8編碼太占存儲(chǔ)空間幌陕,根據(jù)二八定律诵姜,UTF-8采用了一種可變長的編碼方式,即將常用的字符編碼成較短的序列搏熄,而不常用的字符用較長的序列表示棚唆,這樣讓編碼占用更少存儲(chǔ)空間的同時(shí)也保證了對(duì)龐大字符集的支持暇赤。

正式由于UTF-8采用的這種特別的變長編碼方式,這一點(diǎn)和其他的編碼很不一樣瑟俭。比如GBK固定用兩個(gè)字節(jié)來表示漢字,一個(gè)字節(jié)來表示英文和其他符號(hào)契邀。

來測試一下:

import java.io.UnsupportedEncodingException;

public class EncodingTest {

public static void main(String[] args) throws UnsupportedEncodingException {

String srcString = "我們是中國人";

byte[] GbkBytes = srcString.getBytes("GBK");

System.out.println("GbkBytes.length:" + GbkBytes.length);

byte[] UtfBytes = srcString.getBytes("UTF-8");

System.out.println("UtfBytes.length:" + UtfBytes.length);

String s;

for (int i = 0; i < srcString.length(); i++) {

s = Character.valueOf(srcString.charAt(i)).toString();

System.out.println(s + ":" + s.getBytes().length);

}

}

}

運(yùn)行結(jié)果為:

GbkBytes.length:12

UtfBytes.length:18

我:3

們:3

是:3

中:3

國:3

人:3

可以看到使用GBK進(jìn)行編碼摆寄,“我們是中國人”6個(gè)漢字占12個(gè)字節(jié),而是用UTF-8進(jìn)行編碼則占了18個(gè)字節(jié)坯门,其中每個(gè)漢字占3個(gè)字節(jié)(由于是常用漢字微饥,只占3個(gè)字節(jié),有的稀有漢字會(huì)占四個(gè)字節(jié)古戴。)

UTF-8編碼的讀取方式也比較不同欠橘,需要先讀取第一個(gè)字節(jié),然后根據(jù)這個(gè)字節(jié)的值才能判斷這個(gè)字節(jié)之后還有幾個(gè)字節(jié)共同參與一個(gè)字符的表示现恼。

對(duì)于某一個(gè)字符的UTF-8編碼肃续,如果只有一個(gè)字節(jié)則其最高二進(jìn)制位為0;如果是多字節(jié)叉袍,其第一個(gè)字節(jié)從最高位開始始锚,連續(xù)的二進(jìn)制位值為1的個(gè)數(shù)決定了其編碼的位數(shù),其余各字節(jié)均以10開頭喳逛。UTF-8最多可用到6個(gè)字節(jié)瞧捌。?

如表:

1字節(jié) 0xxxxxxx

2字節(jié) 110xxxxx 10xxxxxx

3字節(jié) 1110xxxx 10xxxxxx 10xxxxxx

4字節(jié) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

5字節(jié) 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

6字節(jié) 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

因此UTF-8中可以用來表示字符編碼的實(shí)際位數(shù)最多有31位,即上表中x所表示的位润文。除去那些控制位(每字節(jié)開頭的10等)姐呐,這些x表示的位與UNICODE編碼是一一對(duì)應(yīng)的,位高低順序也相同典蝌。

實(shí)際將UNICODE轉(zhuǎn)換為UTF-8編碼時(shí)應(yīng)先去除高位0曙砂,然后根據(jù)所剩編碼的位數(shù)決定所需最小的UTF-8編碼位數(shù)。

因此那些基本ASCII字符集中的字符(UNICODE兼容ASCII)只需要一個(gè)字節(jié)的UTF-8編碼(7個(gè)二進(jìn)制位)便可以表示骏掀。

上面一隨便看看就好麦轰,只要知道“由于UTF-8的特殊編碼方式,所以有些序列是不可能出現(xiàn)在UTF-8編碼中的”就可以了砖织。

所以當(dāng)我們將由GBK編碼的12個(gè)字節(jié)試圖用UTF-8解碼時(shí)會(huì)出現(xiàn)錯(cuò)誤款侵,由于GBK編碼出了不可能出現(xiàn)在UTF-8編碼中出現(xiàn)的序列,所以當(dāng)我們?cè)噲D用UTF-8去解碼時(shí)侧纯,經(jīng)常會(huì)遇到這種不可能序列新锈,對(duì)于這種不可能序列,UTF-8把它們轉(zhuǎn)換成某種不可言喻的字符“?”眶熬,當(dāng)這種不可言喻的字符再次以UTF-8進(jìn)行編碼時(shí)妹笆,他們已經(jīng)無法回到最初的樣子了块请,因?yàn)槟切┦荱TF-8編碼不可能編出的序列。然后這個(gè)神秘字符再轉(zhuǎn)換成GBK編碼時(shí)就變成了“錕斤拷”拳缠。當(dāng)然墩新,還有很多其他的巧合,可能正好碰到UTF-8中存在的序列窟坐,甚至原本不是一個(gè)字符的字節(jié)海渊,可能是某個(gè)字的第二個(gè)字節(jié)和下一個(gè)字的兩個(gè)字節(jié),正好被識(shí)別成一個(gè)UTF-8序列哲鸳,于是解碼出一個(gè)漢字臣疑,當(dāng)然這些在我們看來都是亂碼了,只不過不是“錕斤拷”的樣子徙菠。因?yàn)椴豢赡苄蛄懈毡榇嬖谘渡颍訥BK轉(zhuǎn)UTF-8再轉(zhuǎn)GBK時(shí),最常見的便是“錕斤拷”婿奔!

所以:以非UTF-8編碼編碼出的字節(jié)數(shù)組缺狠,一旦以UTF-8進(jìn)行解碼,通常這是一條不歸路萍摊,再嘗試將解碼出的字符以UTF-8進(jìn)行編碼儒老,也無法還原之前的字節(jié)數(shù)組。

相反地记餐,其他的固定長度編碼幾乎都可以順利還原驮樊。

=====================2016/11/15補(bǔ)充==========================

上文中其實(shí)有一個(gè)東西一直在回避,就是既然所有字符在保存時(shí)都需要轉(zhuǎn)換成二進(jìn)制片酝,那么java是使用什么編碼來保存字符的呢囚衔?這個(gè)問題其實(shí)我們可以不必深究,因?yàn)檫@對(duì)我們是透明的雕沿,我們只要假設(shè)java使用某種編碼可以表示所有字符练湿。得益于這種透明,我們可以當(dāng)作java是直接保存字符本身的审轮,就如上文所做的這樣肥哎。但是今天面試的時(shí)候被問到了,我說這個(gè)是對(duì)我們透明所以沒有深究疾渣。他說雖然是透明的篡诽,但是如果弄懂其中的原理還是能加深理解。我馬上想到unicode榴捡,因?yàn)閖ava要準(zhǔn)確地表示所有字符杈女,那么只有unicode能勝任了。這個(gè)回答也得到面試官的肯定,還說了一些更細(xì)節(jié)的达椰。每種編碼都會(huì)提供和unicode編碼之間的轉(zhuǎn)換規(guī)則翰蠢。當(dāng)我們以字符串直接量new一個(gè)String,這個(gè)String就是以u(píng)nicode在內(nèi)存中存儲(chǔ)的啰劲。同樣這也解決了一個(gè)讓我疑惑的問題:為什么一個(gè)char中既可以存儲(chǔ)一個(gè)字母梁沧,也可以存儲(chǔ)一個(gè)漢字,明明很多編碼如GBK蝇裤、UTF-8中漢字和字母的長度不一樣廷支。如果java虛擬機(jī)使用unicode編碼,那這一切就很好理解了猖辫,字母和漢字長度一樣酥泞。

新增一條結(jié)論:java虛擬機(jī)中以使用unicode編碼保存字符砚殿,任何編碼都提供了和unicode編碼的轉(zhuǎn)換規(guī)則啃憎。

---------------------本文來自 _古井心 的CSDN 博客 ,全文地址請(qǐng)點(diǎn)擊:https://blog.csdn.net/u010234516/article/details/52853214?utm_source=copy

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末似炎,一起剝皮案震驚了整個(gè)濱河市辛萍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羡藐,老刑警劉巖贩毕,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仆嗦,居然都是意外死亡辉阶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門瘩扼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谆甜,“玉大人,你說我怎么就攤上這事集绰」嫒瑁” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵栽燕,是天一觀的道長罕袋。 經(jīng)常有香客問我,道長碍岔,這世上最難降的妖魔是什么浴讯? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蔼啦,結(jié)果婚禮上兰珍,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好掠河,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布亮元。 她就那樣靜靜地躺著,像睡著了一般唠摹。 火紅的嫁衣襯著肌膚如雪爆捞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天勾拉,我揣著相機(jī)與錄音煮甥,去河邊找鬼。 笑死藕赞,一個(gè)胖子當(dāng)著我的面吹牛成肘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斧蜕,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼双霍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了批销?” 一聲冷哼從身側(cè)響起洒闸,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎均芽,沒想到半個(gè)月后丘逸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掀宋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年深纲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劲妙。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湃鹊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出是趴,到底是詐尸還是另有隱情涛舍,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布唆途,位于F島的核電站富雅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肛搬。R本人自食惡果不足惜没佑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望温赔。 院中可真熱鬧蛤奢,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痹屹,卻和暖如春章郁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背志衍。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工暖庄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人楼肪。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓培廓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親春叫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肩钠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • UTF-8 編碼提供了一種簡便而向后兼容的方法, 使得那種完全圍繞 ASCII 設(shè)計(jì)的操作系統(tǒng), 比如 Unix,...
    謝大見閱讀 4,678評(píng)論 0 3
  • 摘要:本文從Unicode入手蔬将,介紹由于通信問題而產(chǎn)生的字符集爷速,以及Unicode的發(fā)展情況央星。介紹各種字符集的及其...
    瘋狂的冰塊閱讀 1,934評(píng)論 0 6
  • 為什么要編碼 不知道大家有沒有想過一個(gè)問題,那就是為什么要編碼惫东?我們能不能不編碼莉给?要回答這個(gè)問題必須要回到計(jì)算機(jī)是...
    艾小天兒閱讀 17,276評(píng)論 0 2
  • 記憶中,生病的夜廉沮,她都會(huì)來到床前颓遏,噓寒問暖,遞水送藥滞时,她為我暖過腳叁幢,為我嚼用火烤過的姜塊,為我敷上 如今也是生病的...
    泠露閱讀 125評(píng)論 0 0
  • 原創(chuàng) 2017-05-04 趙老師 米叔的米故事 文:趙老師 | 圖:Internet | 編輯:W2D Snow...
    慶波926138閱讀 788評(píng)論 0 1