我保證這是最后一篇了,而且這次的內(nèi)容絕對(duì)都是很具體的,具體得連每篇博客開(kāi)頭例行的摘要我都不知道該寫(xiě)什么了!
用文本編輯器打開(kāi)一個(gè)文件,如果編碼不兼容,有時(shí)候會(huì)看到??????
的東西,有時(shí)候會(huì)看到一團(tuán)亂七八糟的文字,通常我們就統(tǒng)稱亂碼了。怎么用編碼的知識(shí)來(lái)理解呢?
前文中我們有說(shuō)到實(shí)用的很多編碼方式都用的是變長(zhǎng)字節(jié)編碼,很多字節(jié)都要結(jié)合它的上下文去解釋才是對(duì)的。例如:用UTF-8的算法去解析GBK的文件,就很容易發(fā)些這么些種情況:
11111110
開(kāi)頭的字節(jié)序列。反過(guò)來(lái)看,用GBK的算法去解析UTF-8的文件其實(shí)也差不多,遇到第一種情況在顯示的時(shí)候可能就用問(wèn)號(hào)代替,而遇到第二種情況就是出現(xiàn)一些風(fēng)馬牛不相及的雜亂文字。
方塊其實(shí)和問(wèn)號(hào)本質(zhì)上一樣的,但方塊在現(xiàn)代瀏覽器里還有個(gè)很常見(jiàn)的情況,就是一個(gè)字符的編號(hào)在字體當(dāng)中并沒(méi)有定義,于是在排版和渲染的適合“智能”地用一個(gè)方塊來(lái)表示它了??吹椒綁K可以結(jié)合上下文,如果上下文當(dāng)中的非英字符顯示正確的,那么方塊可能是一些特殊符號(hào),比如Emoji。
在寫(xiě)服務(wù)端程序的時(shí)候要小心處理“半個(gè)字符”的問(wèn)題,例如我們?cè)谇凹?jí)對(duì)超長(zhǎng)的數(shù)據(jù)進(jìn)行截?cái)嗵幚?,剛好截?cái)嗟粢粋€(gè)變長(zhǎng)編碼的字節(jié)序列,就會(huì)出現(xiàn)“半個(gè)字符”。一般半個(gè)字符都是鐵定會(huì)亂碼,一些容錯(cuò)比較差的程序甚至?xí)欤热缫恍┳龅牟缓玫腜HP的C擴(kuò)展,嚴(yán)重的時(shí)候會(huì)出core。所以程序不懂編碼就別瞎截,甚至考慮到某些語(yǔ)言文字里的組合字符,就是知道編碼也別瞎截(真是細(xì)思恐極);
BOM就是Browser Object Model瀏覽器對(duì)象模型,不好意思拿錯(cuò)劇本了。
BOM(Byte-Order Mark,字節(jié)序標(biāo)記)是Unicode碼點(diǎn)U+FEFF
。它被定義來(lái)放在一個(gè)UTF-16文件的開(kāi)頭,如果字節(jié)序列是FEFF
那么這個(gè)文件就是大端序,如果字節(jié)序列是FFFE
那么這個(gè)文件就是小端序。
UTF-8本身是沒(méi)有字節(jié)序的問(wèn)題的(因?yàn)樗且詥蝹€(gè)字節(jié)為最小單位),但是Windows里面很多編輯器(比如記事本)會(huì)多此一舉的在UTF-8文件開(kāi)頭加入EF BB FF
也就是U+FEFF
的UTF-8編碼。
如果你的PHP文件里面有一個(gè)這東西你就倒了大霉了,可能會(huì):
于是建議在Windows上做開(kāi)發(fā)的同學(xué),一定要選擇“使用UTF-8無(wú)BOM格式”保存,所以用記事本寫(xiě)代碼裝X就不好使了,用Notepad++的可以注意選一下,它支持的文件編碼格式挺豐富的,用一些比較先進(jìn)的跨平臺(tái)編輯器比如WebStorm、SublimeText它們都是沒(méi)BOM的。
亂碼之所以叫亂碼,就是因?yàn)樗恰皝y”的。但是亂碼當(dāng)中最出名的就是“錕斤拷”,他出現(xiàn)次數(shù)太多了以至于看起來(lái)根本就沒(méi)那么“亂”。這就納了悶了,為什么全中國(guó)的網(wǎng)站亂碼里面都會(huì)有這個(gè)?
原因是,在將一些國(guó)家語(yǔ)言編碼體系,比如GB、BIG-5、EUC-JP等,轉(zhuǎn)換為Unicode的過(guò)程中,多少有一些字符是不在Unicode中的(比如一些偏旁部首在Unicode里是后來(lái)才收錄的),甚至它本身在原來(lái)的編碼體系里面就是非法字符的情況。
Unicode規(guī)定了U+FFFD
當(dāng)作一個(gè)占位符用來(lái)表示這些字符,用UTF-8編碼它就是EF BF BD
,連續(xù)多個(gè)這樣的字節(jié)序列出現(xiàn)就成了EF BF BD EF BF BD
。如果是一個(gè)UTF-8的解析程序還好,而如果用一個(gè)GB的解析程序去打開(kāi),一個(gè)漢字2字節(jié),就成了“錕斤拷”。這里就是一個(gè)例子,用UTF-8編碼打開(kāi)是問(wèn)號(hào),用GBK編碼打開(kāi)的話就會(huì)看到錕斤拷,用hexdump或者UltraEdit這類任何16進(jìn)制編輯器看的話就能看到里面都是EF BF BD
。
要避免錕斤拷一個(gè)重要的點(diǎn)就是盡量減少程序當(dāng)中的編碼轉(zhuǎn)換。比如輸入是UTF-8,但是一個(gè)舊的模塊是GBK,把UTF-8轉(zhuǎn)成GBK交給舊的模塊處理,處理過(guò)程中舊模塊多多少少有些BUG的可能,再轉(zhuǎn)回來(lái)的時(shí)候就容易錕斤拷了。一個(gè)項(xiàng)目的源代碼在團(tuán)隊(duì)里面被不同的人(他們編輯器配置不盡相同)開(kāi)來(lái)開(kāi)去,存來(lái)存去,也很容易出現(xiàn)錕斤拷。
這個(gè)和編碼轉(zhuǎn)換其實(shí)沒(méi)啥關(guān)系,在VC的DEBUG模式下,會(huì)把未初始化的棧內(nèi)存全部填成0xCC
,未初始化的堆內(nèi)存填成0xCD
,這樣做是讓你一眼就能看出來(lái)你開(kāi)了內(nèi)存沒(méi)初始化。
而用GBK編碼的話,CC CC
就是“燙”,CD CD
就是“屯”。
URL Encode又稱為“百分號(hào)編碼”它主要用來(lái)在URI里面將特殊字符進(jìn)行轉(zhuǎn)義,因?yàn)橄?code>/、&
、=
等等這類字符在URI里面本身是有功能性的。
對(duì)于ASCII字符的編碼很簡(jiǎn)單就是用%
后跟ASCII編碼的16進(jìn)制表示,例如/
的ASCII char code是47
,16進(jìn)制表示是2F
,于是它的URL Encode結(jié)果就是%2F
。
對(duì)于非ASCII字符,將它的每個(gè)字節(jié)進(jìn)行相同規(guī)則的轉(zhuǎn)換,例如中文“編碼”的Unicode char code是U+7F16 7801
,UTF-8編碼的字節(jié)序列是E7 BC 96 E7 A0 81
,所以它按照UTF-8編碼的URL Encode結(jié)果就是%E7%BC%96%E7%A0%81
。
可以看出,URL Encode編碼非ASCII字符的時(shí)候,結(jié)果與使用的字符編碼有關(guān)。因此在頁(yè)面上提交表單、發(fā)起Ajax請(qǐng)求等操作的時(shí)候需要注意編碼。瀏覽器會(huì)按照當(dāng)前頁(yè)面所使用的字符編碼對(duì)表單體提交進(jìn)行URL Encode,但使用JavaScript的encodeURI
和encodeURIComponent
的時(shí)候則總是會(huì)使用UTF-8(參考MDN)。
表單提交的時(shí)候編碼是非常非常重要的,一旦錯(cuò)了服務(wù)端解開(kāi)數(shù)據(jù)的時(shí)候就會(huì)跪。比如Github在它們的搜索表單里面放了一個(gè)<input name="utf8" type="hidden" value="?">
,其中那個(gè)對(duì)鉤?是U+2713
,UTF-8編碼是E2 9C 93
,他們可以在服務(wù)端檢測(cè)這個(gè)參數(shù)的值對(duì)不對(duì)從而對(duì)URL里用的編碼進(jìn)行一個(gè)初步檢測(cè)。雖然我沒(méi)有看到他們使用其他編碼的情況,不過(guò)這樣也算是一個(gè)編碼協(xié)商和Check的手段吧。
在JavaScript中使用escape
也可以達(dá)到URL Encode的效果,但是它對(duì)于非ASCII字符使用了一種非標(biāo)準(zhǔn)的的實(shí)現(xiàn),例如“編碼”會(huì)被escape
成%u7F16%u7801
這種%uxxxx
奇怪的表示,W3C把這個(gè)函數(shù)廢棄了,身為一名前端還用是打臉的哦。
Base64是一種用可見(jiàn)字符表示二進(jìn)制數(shù)據(jù)的方法。它用了64個(gè)可見(jiàn)字符[A-Za-z0-9+/]
。
Base64的編碼程序非常簡(jiǎn)單,由于64=2^6,6和8的最小公倍數(shù)是24,也就是3byte,因此對(duì)輸入數(shù)據(jù)以3byte為一個(gè)單位,查表把它轉(zhuǎn)換成4個(gè)可見(jiàn)字符。
如果輸入末尾不足3byte,那就補(bǔ)足,補(bǔ)1個(gè)byte就在輸出末尾添加一個(gè)=
,補(bǔ)2個(gè)byte同理。
Base64經(jīng)常用來(lái)在一些文本協(xié)議里面保存二進(jìn)制數(shù)據(jù),比如HTTP協(xié)議,或者電子郵件的附件啊什么的。同時(shí)因?yàn)樗妮敵鰧?duì)于人類而言不可讀,可以起到一些“混淆加密”的作用,事實(shí)上就有修改64個(gè)字符的排布來(lái)做一個(gè)變形Base64實(shí)現(xiàn)一個(gè)簡(jiǎn)單加密算法的例子。從密碼學(xué)的角度看它基本上沒(méi)什么強(qiáng)度可言,但是足夠簡(jiǎn)單,可以起到防君子不防小人的作用。
由于一個(gè)字符只能編碼6bit,自身卻占了8bit,8/6=1.33,因此使用Base64來(lái)表示數(shù)據(jù)的時(shí)候會(huì)浪費(fèi)1/3的體積。對(duì)于在CSS里面用Base64的data-url方式表示圖片,用之前不妨簡(jiǎn)單估算一下,膨脹的體積和一個(gè)HTTP請(qǐng)求頭比起來(lái)會(huì)相差多少,說(shuō)不定漲太多了已經(jīng)損失掉省一個(gè)請(qǐng)求的收益了。
終于整個(gè)系列都要結(jié)束了,理論的也好,實(shí)用的也好,基本上我覺(jué)得該說(shuō)的都說(shuō)了,要是以后再遇到亂碼,一定會(huì)很快知道問(wèn)題所在。
最后還是要佩服并感謝一下ISO和Unicode聯(lián)盟,做了這么偉大的事情將全世界的語(yǔ)言文字統(tǒng)一收錄和編碼,而這當(dāng)中包括了那么多我們根本沒(méi)聽(tīng)說(shuō)過(guò)的奇怪的語(yǔ)言文字。正是因?yàn)樗麄兊呐Φ於嘶ヂ?lián)網(wǎng)是一個(gè)無(wú)國(guó)界的世界,每天我們都能通過(guò)它獲得來(lái)自任何地方任何語(yǔ)言的信息。
哦,我上面說(shuō)的不是某國(guó)的互聯(lián)網(wǎng)。
聯(lián)系客服