日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

編碼歪傳

 看見就非常 2015-04-24

繼續(xù)上一篇。

身為一名Web開發(fā)者,這一篇將介紹一下在Web應(yīng)用當(dāng)中常會出現(xiàn)編碼問題的地方。文中經(jīng)常會亂用“字符集”和“編碼”,不過看明白了第一篇的話相信你不會混淆概念,而且我個人覺得這兩個概念很多時候混淆也無妨……

概念

出于把問題描述得稍微清楚一點的目的,我打算先把我們的概念進行一下定義。

一般而言我們常遇到亂碼的場景有這樣兩種:

  1. 作為寫入端,我應(yīng)該用什么編碼來存儲/傳輸?
  2. 作為讀出端,我應(yīng)該用什么編碼來消費我所收到的字節(jié)流?

因為我覺得絕大多數(shù)具體場景都可以歸納成上述兩種,所以這樣應(yīng)該可以簡化一下問題。

程序內(nèi)部處理

現(xiàn)代編程語言一般都內(nèi)建字符串作為自帶的數(shù)據(jù)類型,一門強大且又實用的編程語言通常來說都有高效的字符串實現(xiàn)以及大量配套的字符串處理函數(shù)。

在上一篇中我們有順帶提到,UTF-16因為是一種在處理效率和存儲空間之間比較平衡的,同時編碼空間又足夠大的編碼方式,在一些編程語言當(dāng)中被采用來當(dāng)作字符串的內(nèi)部編碼。比如C#、Java(可能因JVM/JDK而異)。

一般而言編程的String類型編碼都是固定的,但是通常會提供豐富的編碼轉(zhuǎn)換函數(shù)。一種(我認(rèn)為)比較可靠的方式是:String用固定編碼方式實現(xiàn),以使得標(biāo)準(zhǔn)的字符串函數(shù)能夠只關(guān)注一種編碼,從而保證它的正確性,也能夠最大程度地針對性優(yōu)化;而通過使用類庫來將String轉(zhuǎn)換為特定編碼的字節(jié)流,或?qū)⒆止?jié)流以特定編碼轉(zhuǎn)換成String。

反過來看,像PHP里的字符串就比較糙,它的編碼有很大問題,如果一個字符串是多字節(jié)的(通過上一篇我們了解到除了ASCII以外基本上常用的編碼都是多字節(jié)的),處理它就要用mb_xxxx系列的函數(shù)。這對編程是一種負擔(dān),因為這樣就意味著String類型對字符的抽象力度不足,還是得花很多精力去關(guān)注字符串的編碼。對于PHP的程序一個辦法就是在整個程序內(nèi)部統(tǒng)一編碼,同時基于此選擇好使用那一組字符串處理函數(shù)(作為項目規(guī)范),避免程序內(nèi)部關(guān)心編碼的問題,只把編碼暴露在與外界交換數(shù)據(jù)的地方。

存儲/傳輸

管你是什么程序,程序所生成的東西總要被消費才有意義(不然就變成烤機程序了)。Web應(yīng)用里最常見的兩種對程序結(jié)果的消費方式,一是把它存儲(數(shù)據(jù)庫、文件)起來,二是把它傳輸給用戶(瀏覽器)以供展現(xiàn)。

當(dāng)需要存儲/傳輸文本的時候,就需要高度關(guān)心字符編碼了。

存儲

很多人遇到的問題是把用戶表單提交的東西寫進MySQL里面以后亂碼了,這個問題一些可能的原因有:

  1. 提交內(nèi)容的字符編碼
  2. 服務(wù)端程序(如PHP)內(nèi)部使用的編碼
  3. MySQL傳輸時候使用的編碼
  4. MySQL數(shù)據(jù)庫聲明和使用的字符集

第1點下一步會更詳細的展開。

第2點在上文當(dāng)中有一定介紹了,PHP程序所接收的字節(jié)流被當(dāng)作字符串看待后,我們的程序必須要選擇合適的字符串處理函數(shù),結(jié)果才會是對的。比如一個截斷程序要能正確處理多字節(jié)編碼,如果把多字節(jié)編碼切斷成“半個字符”嚴(yán)重的時候甚至?xí)斐蒔HP出core。

第3點就是PHP中常見的mysqli_set_charset所覆蓋的范圍,沒錯,因為MySQL其實是服務(wù),所以這個存儲其實也是傳輸。

第4點就是在建庫建表的時候選的那個字符集和編碼。

這當(dāng)中的重點的就是2需要對1的編碼有預(yù)期,正確的把1的字節(jié)流解析出來,轉(zhuǎn)換成程序內(nèi)部字符串實現(xiàn)所使用的編碼,套用正確的算法,接下來與MySQL驅(qū)動和服務(wù)之間使用雙方預(yù)期的編碼,最終以數(shù)據(jù)庫定義的時候所聲明的字符集保存下來。

傳輸

一個HTTP請求發(fā)出的時候,用戶代理(UserAgent,通常是瀏覽器)可以通過HTTP Request Header中的Accept-Charset字段來顯式聲明預(yù)期返回的編碼,這是一種協(xié)商手段?,F(xiàn)在的瀏覽器都很流弊,啥編碼都能解析,于是直接懶得發(fā)這個,言下之意就是服務(wù)端給返回什么就消化什么。

對于服務(wù)端而言,如果收到的請求指定了Accept-Charset那么應(yīng)該按照請求者的預(yù)期來決定響應(yīng)內(nèi)容的編碼,如果沒有指定,則可以“自由發(fā)揮”,這種時候理論上說你用什么編碼都可以,但最終都必須通過某種手段告訴請求者響應(yīng)內(nèi)容是什么編碼。

方式1:使用HTTP Response Header中Content-Type來給響應(yīng)內(nèi)容聲明編碼。比如Content-Type: text/html; charset=UTF-8。這里有個小插曲,在IE6(沒記錯的話)里用Ajax請求的時候如果Response寫的是小寫utf-8就會跪,必須要大寫。別問我為什么知道,說起來都是淚,那是一個風(fēng)雨交加的深夜……

方式2:通過HTML頁面頭部的<meta charset="xxx">標(biāo)簽來給頁面聲明編碼。如果Response Header里不寫編碼,瀏覽器就會嘗試找這個標(biāo)簽,然后將接下來的內(nèi)容以這個編碼解讀。這就是為什么我們提倡將<meta charset="xxx">寫在<title>標(biāo)簽之前的原因,如果<title>出現(xiàn)在此之前,它里面的字符就不知道該用什么編碼來解讀了,直觀的說就是可能造成title亂碼。

一旦決定了編碼,服務(wù)端程序就會將字符以該種編碼最終寫入字節(jié)流,傳給客戶端。

那如果兩種方式都用了,口徑卻不一致會怎么辦?首先當(dāng)然是給開發(fā)者賞兩耳光,然后有興趣的可以做做實驗看看不同的瀏覽器會有什么不同的兼容策略。

用戶提交內(nèi)容

上面有說表單提交也有個編碼的問題,其實包括Ajax請求等,只要是客戶端向服務(wù)端發(fā)送內(nèi)容,都一樣,但通過上面的例子我想你已經(jīng)明白了,這完全是鏡像的,這次瀏覽器扮演著信息的生產(chǎn)者的角色,本質(zhì)是完全一樣的。

消費

給你一本書,你怎么知道它是中文版還是英文版?“我靠,它用英文寫的就是英文版,用中文寫的就是中文版啊。”

人類的大腦簡直聰明得要命了,這種問題根本不需要動腦子,計算機就要笨多了。其實并不是計算機笨,而是這個問題在計算機的領(lǐng)域里面太難了。比如上一篇文章說到GB2312是兼容ASCII的,那么如果收到的內(nèi)容前幾個字節(jié)是3C 68 74 6D 6C 3E也就是<html>的ASCII編碼,也許臆想它是ASCII的,于是后面出現(xiàn)的雙字節(jié)字符可能就會遭殃了。UTF-8有一個很不錯的性質(zhì)是它比較容易識別,但是也有錯誤率和效率問題。所以這些你猜來我猜去的不靠譜的倒霉事情就只讓它出現(xiàn)在男女情愛當(dāng)中吧不要來污染我們純凈的計算機世界了好嗎。

上面一節(jié)當(dāng)中有說到,一個靠譜的信息生產(chǎn)者,會在給你傳遞信息的時候協(xié)商或聲明編碼。身為一個合格的信息消費者,瀏覽器可以通過這些聲明來選擇正確的編碼,解讀字節(jié)流。

瀏覽器也是個程序,于是它內(nèi)部也會有字符串實現(xiàn),也許它用自帶字符串的語言實現(xiàn)的,也許它用自己實現(xiàn)的字符串(如C/C++),不管怎樣,有了明確的編碼,瀏覽器都能夠?qū)⑺@得的字節(jié)流轉(zhuǎn)換成自己所使用的內(nèi)部編碼。

事已至此,似乎只要生產(chǎn)者靠譜,消費者要注意的問題就非常少了。在服務(wù)端我們小心翼翼地處理那么多環(huán)節(jié)的編碼問題,到了瀏覽器好像已經(jīng)完事兒了。不管這之前有再多波折,瀏覽器內(nèi)部各種對字符的處理再多,基本上都不會有編碼的問題了,簡直太沒勁了,于是這里稍微發(fā)散思維一下。

接下來瀏覽器就需要把字符顯示出來,我們考慮瀏覽器通過操作系統(tǒng)給它提供的API。API要么規(guī)定編碼要么協(xié)商/聲明編碼對吧,如果是前者,瀏覽器需要把自己內(nèi)部用的編碼轉(zhuǎn)換成API所預(yù)期的編碼,然后調(diào)用API——在這個場景里面,瀏覽器又從信息的消費者變成生產(chǎn)者了對吧,而這次操作系統(tǒng)是消費者。

然后我們假設(shè)操作系統(tǒng)將會用某種字體渲染這段字符,字體文件內(nèi)部一般都對每個字符進行編號,現(xiàn)代的字體一般都會用Unicode,沒錯,我們又回到了字符集的概念。操作系統(tǒng)將字符編碼還原到字符集當(dāng)中的字符編號(顯然對于變長字節(jié)編碼這個過程要一些運算),在字體文件內(nèi)通過編號查到這個字符,一個設(shè)計良好的字體可能對同一個字符會設(shè)計了多個字形(Glyph),比如Regular體一個、粗體一個、斜體一個,甚至還有更多更多,比如組合字符、一些特殊規(guī)則下的變形字符,不展開討論。

這些渲染規(guī)格都是在API里指定好的,然后就用對應(yīng)的字形來進行渲染。渲染字形這事兒還不是一個簡單的事情,字體分點陣的、矢量的(甚至圖片的?),不同的渲染引擎,例如Windows上的GDI、DirectWrite、第三方的GDI++、MacType,還有OSX的渲染引擎,Linux不同的桌面系統(tǒng)的渲染引擎,在最終把字形繪制成像素點的算法上有細節(jié)區(qū)別。

上面說的還只是渲染單個字符的時候的問題,在此之前還要做文字的排版啊什么的,哪怕看起來很小一件事情也夠人鉆大半輩子了。我的天,人類為了在計算機上展示文字到底下了多少功夫?

好的好的,剛才似乎發(fā)散的太多了,就此打住,總之就瀏覽器而言對于一個HTML頁面的消費差不多是可以理解了。

階段性小結(jié)

把亂碼的問題從一個信息的生產(chǎn)者和消費者兩個角度來看,中間所經(jīng)歷的哪些環(huán)節(jié)涉及到編碼,哪些環(huán)節(jié)涉及到編碼的協(xié)商與聲明,就明確多了。上面的例子其實很容易就可以舉一反三。

于是一些常見的諸如“PHPMyAdmin里看是正常的,頁面上是亂碼”或者“頁面上是正常的,PHPMyAdmin里看著是亂碼”這種問題可能會是哪些個環(huán)節(jié)闖的禍心里就已經(jīng)有譜了。對于各種接口,比如與MySQL通訊,比如與后端之間的接口,如何協(xié)商/聲明編碼,什么時候需要轉(zhuǎn)換編碼,心里面也有譜了。

預(yù)報

呵呵呵呵,這次的內(nèi)容雖然沒那么理論,但是還是太簡單了嘛,看到亂碼就查編碼唄你當(dāng)我是傻X呢。

這時候也有觀眾吐槽:“那么各種程序當(dāng)中用的編碼比如URL Encode、Base64又是些啥玩意啊老濕?”

也有好奇心過盛的觀眾要問:“問號和方塊是怎么回事?屯屯屯燙燙燙錕斤拷又是些什么鬼呢老濕?”

對于上面的問題我只想說四個字:請聯(lián)系我請看終篇:《編碼歪傳——番外篇》

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多