如何理解字符编码
一直有個(gè)困惑,為什么計(jì)算機(jī)系統(tǒng)搞那么多字符編碼,就一個(gè)Unicode統(tǒng)一天下不就得了,后來看了篇文章,才多少理解一丁點(diǎn)。
英語的國家,只要一個(gè)字節(jié)就可以表示全部的字符,一個(gè)無符合的字節(jié)可以表示256個(gè)字符,對于英語的國家而言綽綽有余了。但是其它國家的字符就費(fèi)勁了,如果使用Unicode編碼,囊括全部的字符,要統(tǒng)一天下,那么一個(gè)字節(jié)就遠(yuǎn)遠(yuǎn)不夠了,需要很多字節(jié)來表示一個(gè)字符(比如4個(gè)字節(jié)或者更多字節(jié))才行,因?yàn)槿虻淖址麑?shí)在TMD太多了。
好了咱們就這么定吧,Unicode編碼來表示全部的字符好嗎?居然有人反對了,說英文字母只用一個(gè)字節(jié)表示就夠了,如果Unicode統(tǒng)一規(guī)定,每個(gè)符號(hào)用三個(gè)或四個(gè)字節(jié)表示,那么每個(gè)英文字母前都必然有二到三個(gè)字節(jié)是0,這對于存儲(chǔ)來說是極大的浪費(fèi),文本文件的大小會(huì)因此大出二三倍,這是無法接受的。總之就是沒法達(dá)成一致,既然如此,那么就各家自己設(shè)計(jì)字符編碼方案吧,于是乎才有那么一堆UTF-8,GB2312,ISO 8859-1,GBK等等。
但是計(jì)算機(jī)內(nèi)存只能保存Unicode編碼,這點(diǎn)則是統(tǒng)一的,而且必須統(tǒng)一,否則問題就大了。
如果沒有Unicode,會(huì)怎么樣,舉栗子說明下:
中國使用自己的字符集編碼GBK對字符串china編碼,假設(shè)編碼后的二進(jìn)制碼就是:0111 0101,那么計(jì)算機(jī)就按0111 0101存儲(chǔ)china。那么美國用戶想在他們自己的計(jì)算機(jī)打開文件查看,就必須使用GBK解碼(他們的計(jì)算機(jī)必須安裝GBK編碼)才能看到china,這是肯定的。那么這種編碼和解碼的關(guān)系如下圖所示:
計(jì)算機(jī)是根據(jù)GBK編碼把中文字符串轉(zhuǎn)換成二進(jìn)制碼后進(jìn)行存儲(chǔ)的,假設(shè)GBK編碼表如下圖所示:
如上圖可以明確知道,字符編碼就是規(guī)定每個(gè)字符對應(yīng)的二進(jìn)制碼是什么。
文檔的內(nèi)容其實(shí)是英文字母,對于美國用戶而言他們習(xí)慣使用UTF-8字符編碼,他們的計(jì)算機(jī)默認(rèn)是使用UTF-8編碼和解碼的,他不希望每次打開這份文檔都要指定GBK解碼才能正常看內(nèi)容,那么就必須把這份文檔改成UTF-8編碼保存才行,這時(shí)候問題來了,沒有中間碼(Unicode),那么怎么把文檔的編碼改成UTF-8呢?因?yàn)樽址甤hina在UTF-8的規(guī)則里面,對應(yīng)的二進(jìn)制碼是1010 0010,如下圖所示:
就是說,計(jì)算機(jī)是根據(jù)GBK的規(guī)則把0111 0101轉(zhuǎn)換成字符串china顯示給用戶看的,也就是說計(jì)算機(jī)對文檔的數(shù)據(jù)理解就是根據(jù)GBK編碼規(guī)則來理解的。現(xiàn)在要按UTF-8保存文檔,就是把china轉(zhuǎn)換成10100010存儲(chǔ),那么01110101如何變成10100010呢?沒法變,對吧!所以沒有中間碼(Unicode)就無法把GBK的文檔變成UTF-8的文檔。
再舉個(gè)栗子說明下:
計(jì)算機(jī)和編程語言都是西方國家發(fā)明的,這些計(jì)算機(jī)對編程語言的理解是基于二進(jìn)制碼,因此計(jì)算機(jī)對這些二進(jìn)制碼的含義認(rèn)知應(yīng)該已經(jīng)固定和達(dá)成統(tǒng)一了,計(jì)算機(jī)的理解過程用下圖簡單表示:
上面的代碼根據(jù)計(jì)算機(jī)本來設(shè)計(jì)的編碼規(guī)則理解,可以得到如下圖所示的字節(jié)碼:
就是說JVM遇到0110,知道是int數(shù)據(jù)類型,遇到0010知道是=,遇到0111知道是+,所以根據(jù)這樣的理解,最終計(jì)算機(jī)知道代碼的含義,從而計(jì)算得到結(jié)果2并且打印輸出。
如果計(jì)算機(jī)沒有這樣默認(rèn)的編碼規(guī)則,那怎么玩?GBK,UTF-8等等這些編碼方案都是后面才設(shè)計(jì)出來的,而且相同的二進(jìn)制碼對應(yīng)不同的編碼方案其含義還完全不同,所以你讓系統(tǒng)的指令集如何去理解這些二進(jìn)制代碼。舉個(gè)比較形象的栗子:好比計(jì)算機(jī)是個(gè)“神人”,中國用戶用自己的語言把需求描述清楚了,日本用戶用自己的語言也把需求描述清楚了,這兩份需求要提交給“神人”去完成,“神人”怎么理解這兩份需求呢?你是不是要翻譯成“神人”理解的語言才行呀!“神人”只理解Unicode的,所以中國和日本用戶必須把需求翻譯成Unicode后,“神人”就理解兩位用戶的需求是啥了,于是“神人”就用自己的能力去完成需求的任務(wù),得到兩位用戶想要的結(jié)果。你如果不要這個(gè)“神人”幫忙,想自己完成特定的需求任務(wù)可以嗎?當(dāng)然可以,那你自己去創(chuàng)造個(gè)“神人”,來理解你的需求就行了。也就是說你自己創(chuàng)造個(gè)“機(jī)器”,它可以理解你用自己特定的編碼規(guī)則提交的代碼從而完成相應(yīng)的任務(wù),而且這個(gè)“機(jī)器”只能理解你的代碼,日本用戶的代碼理解不了,德國的代碼也理解不了。這樣你覺得滿意嗎?
所以說計(jì)算機(jī)必須有個(gè)初始的、固定的編碼規(guī)則,而GBK、UTF-8等編碼規(guī)則都只是把自己國家的字符所對應(yīng)的Unicode轉(zhuǎn)換成自己的字符編碼而已。如下圖所示:
我再舉個(gè)例子,大家如果理解了就基本ok了,假設(shè)瀏覽器指定字符編碼是UTF-8,那我們看看String str = "小妹"的編碼是如何變化的:
代碼如下:
String c = "小妹";byte[] bs = c.getBytes("UTF-8"); // ① 使用UTF-8對"小妹"進(jìn)行編碼,得到UTF-8編碼String c1 = new String(bs, "ISO-8859-1"); //② 使用ISO-8859-1對UTF-8的編碼進(jìn)行解碼,得到錯(cuò)誤的Unicode編碼byte[] bs1 = c1.getBytes("UNICODE"); // 獲取錯(cuò)誤的Unicode編碼,其實(shí)就是字節(jié)數(shù)組byte[] bs2 = c1.getBytes("ISO-8859-1"); //③ 把錯(cuò)誤的Unicode編碼,重新轉(zhuǎn)換成原來的UTF-8編碼,String c2 = new String(bs2,"UTF-8"); // ④使用UTF-8重新解碼得到正確的Unicode編碼System.out.println(c2); // ⑤小妹解讀如下:
字符串“小妹”在內(nèi)存中的Unicode編碼是:11111110 11111111 01011100 00001111 01011001 10111001
接著瀏覽器使用UTF-8對“小妹”的Unicode進(jìn)行編碼得到UTF-8編碼:11100101 10110000 10001111 11100101 10100110 10111001,接著就傳送給了服務(wù)器
服務(wù)器默認(rèn)使用ISO-8859-1解碼,得到了錯(cuò)誤的Unicode編碼:11111110 11111111 00000000 11100101 00000000 10110000 00000000 10001111 00000000 11100101 00000000 10100110 00000000 10111001
如果想要獲得正確的Unicode編碼,必須把錯(cuò)誤的Unicode編碼重新編碼成原來的UTF-8編碼,因?yàn)樵瓉斫獯a是用ISO-8859-1,所以錯(cuò)誤的Unicode編碼要轉(zhuǎn)換成原來的UTF-8編碼,就必須使用ISO-8859-1進(jìn)行編碼才行,得到了UTF-8編碼之后,再使用UTF-8重新解碼成正確的Unicode編碼就可以了。
上述編碼的變化過程可以看下圖:
P.S 解碼行為就是編碼行為,為了區(qū)別不同的編碼方向和編碼含義,所以規(guī)定字符從初始編碼轉(zhuǎn)換成其它編碼叫編碼,而從其它編碼轉(zhuǎn)換成初始編碼叫解碼
任何字符在計(jì)算機(jī)內(nèi)存中都默認(rèn)以Unicode編碼存在,所以“祖國”這個(gè)字符串初始是以Unicode編碼存在于內(nèi)存中的,那么java程序用UTF-8把“祖國”從Unicode(就是字節(jié)數(shù)組)轉(zhuǎn)換成UTF-8編碼(就是字節(jié)數(shù)組)的過程叫“編碼”,java程序再用UTF-8(就是字節(jié)數(shù)組)將“祖國”從UTF-8編碼轉(zhuǎn)換回Unicode編碼(就是字節(jié)數(shù)組)的過程叫“解碼”。過程如下圖所示:
通常字符是以某種編碼保存在硬盤(磁盤)中,例如GBK,UTF-8,ISO-8859-1等,但是java程序在讀取字符到內(nèi)存時(shí),就必須以Unicode編碼存放,換句話說java程序在內(nèi)存中處理字符的時(shí)候,會(huì)用到Unicode編碼,持久化存儲(chǔ)的時(shí)候都是以其它編碼方案保存。
用圖表述如下:
磁盤文件(假如以UTF-8編碼保存)加載至內(nèi)存,JVM會(huì)以UTF-8解碼轉(zhuǎn)成Unicode編碼。
out.println()寫入response,容器會(huì)將response中的數(shù)據(jù)發(fā)送給瀏覽器,這樣數(shù)據(jù)會(huì)離開服務(wù)器內(nèi)存一段時(shí)間再到用戶電腦的內(nèi)存中,Unicode只在內(nèi)存中出現(xiàn),所以寫入response也要按某種字符編碼(假如也是UTF-8)對文檔的數(shù)據(jù)進(jìn)行編碼后保存,瀏覽器拿到服務(wù)器發(fā)送過來的數(shù)據(jù)后,會(huì)以UTF-8解碼成Unicode編碼,再正常顯示出來。
總結(jié)
- 上一篇: IntelliJ IDEA for Ma
- 下一篇: IntelliJ IDEA for Ma