第4篇 關於Unicode和字符集,每個軟體開發人員都確定、一定以及肯定要知道的最最最起碼的東西(謝絕討價還價!)(初稿)

許青松發表於2013-10-24

致編輯:關於標題的翻譯——(確定、一定以及肯定)使用了對稱原文的眾多語氣副詞,(最最最起碼)用詞性轉換強調原來的(minimum)

日期:2003年10月8日,週三

你是否曾經迷惘於神祕的Content-Type標籤? 你知道啦,就是你得放在HTML程式碼中的那個標籤,而你從沒太搞明白它到底應該是什麼?
你是否曾經收到過保加利亞朋友的電子郵件,而郵件標題卻是“???? ?????? ??? ????”?

enter image description here

我沮喪地發現,居然有那麼多軟體開發人員其實並沒有完全理解字符集、編碼、Unicode等等這些東西。就在幾年之前,FogBUGZ beta版的一名測試人員問它能否處理日文郵件。1日文?他們會收到日文郵件嗎?我不知道。不過我還是認真研究了我們用來解析MIME郵件的那個商業ActiveX控制元件,結果卻發現這個控制元件處理字符集的方式完全是錯誤的,於是我們乾脆寫程式碼先撤銷它的錯誤轉換,然後再正確地轉換一次。我又研究了另外一個商業程式碼庫,其處理字符集的程式碼同樣是一塌糊塗。我聯絡上了這個工具包的開發人員,他的反應大概也是愛莫能助。就像很多程式設計師一樣,他只盼著這些問題會自己銷聲匿跡。

1. .FogBUGZ的簡要說明——譯者草注

但是,它們不會的!後來我又發現即使流行的網站開發工具PHP都幾乎完全無視字元編碼問題,它草率地使用8個位元來表示字元,這怎麼可能開發出優秀的國際化Web應用呢?!我想,夠了,真是受夠了致編輯:(web development tool)更多的習慣譯法是(Web開發工具),這裡考慮到期待受眾是更廣泛的普通非專業讀者,故譯成(網站開發工具);(web application)則屈從於(Web應用)的普及性太強;(darned near impossible)懷疑darn是副詞darned,因此不用(近乎不可能),而用(無限接近於不可能)表現出作者的話嘮調侃屬性。
因此,我在此宣告:如果你今天還是一位在職程式設計師,但你卻不知道關於字元、字符集、編碼及Unicode的基礎知識,那麼別讓我逮到你!一旦被我逮到,我就把你關在潛水艇裡剝上六個月洋蔥以示懲戒!2致編輯:(working in 2003 我看到的原文是working in the twenty-first centrury )因對照本文寫作時間而作改動為(今天還是一位在職程式設計師);(and I catch you)進行了句式變換以便敘述更流暢;(in a submarine)的表達有所擴充,這是為了省去譯者注,讓閱讀更流暢。我發誓,我一定會那麼做的!

2. .剝洋蔥時散發的氣味嗆人難擋,如果關在密封的小空間中,再剝上六個月,那簡直慘不堪言——譯者草注

哦,再宣佈一件事:

這其實並沒有那麼難。

本文將直截了當地向你灌輸那些每位在職程式設計師都應該知道的東西。關於“純文字=ascii=每個字元佔用8個位元”的論調不僅僅是錯誤的,而且錯得無可救藥,如果你仍然這樣程式設計,那你比一位不相信細菌存在的醫生也好不了多少。3拜託,請在讀完本文之前千萬別再寫哪怕一行程式碼!

3. 古代醫生類似於巫醫,會用到水蛭、咒語,隨著科學的發展,現代醫學認識到微生物細菌才是致病及治病就醫的關鍵——譯者草注

在開始之前,我先打個招呼:如果你湊巧是為數不多的瞭解國際化的那幾個人之一,可能會發現這裡的整個討論有那麼一點點過於簡化。我其實只是想在這裡設立一個最低門檻,好讓大家都能理解這是怎麼一回事,從而讓寫出的程式碼有可能正確處理任意語言,而不僅僅是隻能處理那個小得可憐的甚至不含注音字母的英文字符集。致編輯:這裡擴充了一些文字以說明(the subset)這個特指子集另外,我再說明一點:字元處理只是建立國際化軟體漫漫長征中的一小步,但飯只能一口一口吃,我也只能一點一點寫,所以,今天只談字符集。加了(飯只能一口一口吃)是為了讓後面(一點一點寫)不那麼突兀

溯源之旅

要想搞清楚這些,最簡單的方法就是溯古撫今。
你可能覺得我會從古老的EBCDIC(Extended Binary Coded Decimal Interchange Code,擴充二十進位制交換碼)開始講起。4放心吧,我不會講它。EBCDIC跟我們的生活毫無瓜葛。我們也沒有必要回溯得那麼久遠。

4. EBCDIC是一種過渡性字符集,後來有了統一的ASCII,就廢棄不用了——譯者草注

enter image description here 致編輯:此圖可以換成表格並標註各控制字元的含義

在計算機的中古時代,Unix剛剛發明,K&R還在寫他們的《C程式語言》5,一切都至簡不繁。致編輯:(very simple)譯為(非常簡單)會失去(簡潔、乾淨利落)的意思EBCDIC正漸漸退出歷史舞臺。唯一需要考慮的就是當年那些不帶注音的英文字母,人們為此搞出了一套名為ASCII的編碼,使用數字32至127表示了所有的字元。32是空格,65是字母“A”,等等。用7個位元即可方便地儲存這樣的數字。那個年代,大多數計算機都使用長度為8個位元的位元組,於是一個位元組不僅僅能儲存所有可能的ASCII字元,而且還空閒了整整1個位元,如果你居心不良,完全可以將它用於自己那不可告人的目的:WordStar用到了這種不夠敞亮的小點子,它會點亮那個高位位元以標示單詞的最後一個字母,而這限定了WordStar的眼界只能止步於英文。小於32的那些編碼叫做不可列印字元,它們通常用於施咒——開個玩笑啦!這些編碼用作控制字元,譬如7會讓計算機發出嗶嗶的聲音,12會讓印表機走紙,然後裝入下一張紙。

5. C語言的開山大作,從此開始了C的輝煌征程——譯者草注

迄今為止都還不錯,但前提是你得說英文。

enter image description here

由於每個位元組有多達8個位元的儲存空間,所以很多人會想,“啊哈,我們可以把碼值在128至255之間的這些編碼拿來中飽私囊嘛!”麻煩的地方在於,很多人都同時打著這樣的算盤,而他們對這段碼值空間內應該放些什麼都各懷鬼胎。致編輯:(各懷鬼胎)比(各持己見)、(有著自己的想法)多了些作者的戲謔之意IBM-PC搞出了一種後來被稱之為OEM字符集的東西,這種字符集針對歐洲語系提供了一些注音字元和一堆畫線字元(橫線、豎線、左端帶小拐角的橫線、上端帶小拐角的豎線、……)致編輯:為了補足原文省略號的言外之意,加上了(上端帶小拐角的豎線)這句以便讓讀者自行類推,這些畫線字元可以用來在螢幕上繪製出整潔漂亮的條條框框——現在乾洗店裡仍沒淘汰的8088古董電腦上仍然能看到這些線框藝術品。6事實上,美國一旦開始出口電腦,就會萌發臆造出各種不同的OEM字符集,所有這些字符集都會自作主張地使用高階那128個字元。舉個例子,編碼為130的字元在某些PC上會顯示為é,但在銷往以色列的電腦上卻會顯示為希伯來字母ג(這是希伯來語字母表的第三個字母Gimel),於是,當美國人把自己的résumé(個人履歷)發往以色列時,送達時卻變成rגsumג。很多時候,譬如對俄語而言,甚至存在很多不同的實現高階128個字元的思路,這樣即便都是俄語文件也無法放心大膽地相互傳閱。致編輯:(reliable interchange)直譯為(可靠交換)對非專業讀者來說有閱讀障礙,故作此變通

6. IBM-PC指IBM推出的個人電腦,因這些電腦銷售往不同的國家,故IBM針對不同的國家搞出了不同的字符集,即OEM,Original Equipment Manufacturer,貼牌機器的字符集——譯者草注

終於,這場人人得而享之的OEM免費盛宴因ANSI標準而趨於有法可依。根據ANSI標準,所有人都一致認可低端128個編碼的實現方案——該方案几乎和ASCII一模一樣,但根據居住地的不同,允許存在很多不同的方式來處理編碼為128及以上的那些高階字元。這些不同的編碼機制就叫做內碼表。於是,舉例來說,以色列購買的DOS會使用862內碼表,而希臘使用者會使用737內碼表。這些內碼表對於128以下的編碼完全相同,但自128以上的內容則各不相同——所有那些妙趣橫生的字母都放在這裡。MS-DOS的多國版本帶來了數十種這樣的內碼表,它們可以處理從英語到冰島語的所有文字,甚至還有幾種“多語種”內碼表,可以實現在一臺電腦上同時處理世界語和加利西亞語!7哇哦!但是,讓諸如希伯來文和希臘文之類的兩種文字共處一臺電腦仍然是完全不可能的,除非你自己編寫定製程式以使用點陣圖方式顯示所有內容——因為希伯來文和希臘文對高階編碼的解釋有所不同,而這要求使用不同的內碼表。8

7. 世界語及加利西亞語的淵源及此內碼表說明——譯者草注
8. 為什麼要定製程式及使用點陣圖方式呢?這要從計算機為什麼能處理文字說起。錄入>查內碼表>找字型檔實現——譯者草注

與此同時,亞洲發生的事情甚至更加瘋狂,這裡需要考慮到亞洲語系動輒存在上千個字元這一事實,而這麼多字元是完全不可能擠進8個位元的。為了解決問題,通常會用到一套名為“雙位元組字符集(DBCS, Double Byte Character Set)”的複雜系統;在這套編碼系統中,某些字元會儲存為一個位元組,其他字元均儲存為兩個位元組。很容易在字串中作自前向後的移動,但自後向前的移動卻無限接近於不可能。9在字串中前後移動時,不建議程式設計師們使用s++/s--語句,而是建議呼叫專用函式,諸如Windows函式AnsiNext/AnsiPrev,這些函式知道如何理清這一團亂麻。

9. 因為自後向前移動時,無法判定是從雙位元組字元的邊界位元組開始,還是從這兩個位元組中段開始——譯者草注

然而,大多數人還在自欺欺人地認為,一個位元組就是一個字元,一個字元佔用8個位元;如果你從來不在不同電腦之間來回倒騰文字,或者你永遠只說一種語言,那麼你確實可以奉之如圭臬。不過,網際網路一出現,自然而然就非常頻繁地要把文字從一臺電腦轉移至另一臺電腦,而關於內碼表的這團亂麻終於病入膏肓。致編輯:(the whole mess came tumbling down)原來用(一地雞毛終將塵埃落定),但這與後面Unicode登場這句對不上邏輯。幸運的是,這時已經發明瞭Unicode。

Unicode

Unicode是一次勇敢的嘗試,它致力於建立一個字符集來囊括這個星球上所有有據可查的書寫系統,甚至還包括一些生造臆想的文字系統,如克林貢語。10有些人誤以為Unicode只是一種長度為16個位元的編碼,這裡每個字元都佔用16個位元,因此一共存在65536個可能的字元。這其實並不對!鑑於這是最通行的Unicode神話之一,如果你也這麼想,請別覺得難過。

10. 克林貢語是星際迷航Startrek中某外星種族操持的一種語言——譯者草注

事實上,Unicode只是一種不同的思考字元的方式,你必須理解Unicode思考事物的方式,否則這一切毫無意義。
迄今為止,我們一直認為字元會對映成磁碟或記憶體中儲存的位元串:致編輯:(bit)作單位名詞為(位元),作一般性名詞為(二進位制位),另,有無必要以譯者注形式普及bit,byte等基本儲存單位?

A -> 0100 0001

對於Unicode,字元會對映成一種叫做碼點的東西,而碼點仍然只是理論上的一種存在。 記憶體或磁碟中如何表示這種碼點呢?這完全是另外一個故事了,這裡且按下不表。致編輯:(nuther)是(neither)的變體,目前的表述似乎不夠簡潔
在Unicode中,字元A就像柏拉圖的理想國,它只是不接地氣地飄蕩於九天之上:

A

這個柏拉圖式的A 不同於B,也不同於a,但等同於A、A以及A。致編輯:這裡印刷時要注意字型儘量差異明顯 Times New Roman字型下的A與Helvetica字型下的A是同一個字元、但不同於小寫字元“a”,這種看法似乎沒什麼異議;但對於某些語系,即使是界定字元是什麼這樣簡單的問題都會引起紛爭。致編輯:印刷時這兩個A的字型應有所區別 德語字母ß究竟是一個真正的字母?還只是ss的一種變體寫法?11 如果詞尾字母形狀有所變化,它是否變成了另一個字母? 希伯來文認為“是”,而阿拉伯文回答“否”。 不管怎樣,在數十年之前,Unicode協會的那些智者就已經解決了這些問題,儘管當時充滿了大量外交辭令般的論戰,但畢竟現在你無需再擔心這個了。(highly political debate)的翻譯有待商榷 塵埃早已全部落定。

11. ß這個德語字母正逐漸被廢棄,瑞士德語已明確規定用ss來代替ß——譯者草注

Unicode協會對各個語系中每個柏拉圖式的字元都指派了一個神奇的數字,這個數字的寫法形如: U+0639。 這個神奇的數字稱作碼點。 U+代表著“Unicode”,其後的數字為十六進位制數。 U+0639為阿拉伯語字母ع(阿拉伯語字母表的第18個字母Ain)。 英語字母A則對應著U+0041。 使用Windows 2000/XP系統中的charmap(字元對映表)實用工具,或訪問Unicode官方網站(http://www.unicode.org),均可找到所有這些字元。 Unicode能定義的字元數沒有具體的限制,事實上,Unicode已定義的字元數早就遠遠超過了65536個,因此,其實並非所有的Unicode字元都能擠成兩個位元組——不過,這本來就是一個神話哈!
好了,現在假設我們有一個字串:

Hello

這個字串在Unicode中對應著以下5個碼點:

U+0048 U+0065 U+006C U+006C U+006F

只是一堆碼點而已。 其實就是一堆數字。 迄今為止,我們仍未談及如何在記憶體中儲存這種碼點,也從未談到如何在郵件訊息中表示這種碼點。

編碼

終於該編碼登場了。
Unicode的最初編碼方案是,“嘿,我們就按一個數字佔兩個位元組來儲存這些數字好啦”,正因如此才帶來了那個Unicode雙位元組神話。 於是,“Hello”變成了:

00 48 00 65 00 6C 00 6C 00 6F

對嗎? 別那麼快下結論! 它難道不能變成:

48 00 65 00 6C 00 6C 00 6F 00?

好吧……從技術上看,沒錯,我確實認為這行得通,而事實上那些先行者們確實希望能把Unicode碼點儲存為HE模式或LE模式,究竟哪種模式能讓他們那顆特別的CPU執行得最快或最慢,究竟哪種模式更適用於早晨或晚上,這些都已經不重要了;重要的是,現在已經存在兩種儲存Unicode的方法。12 於是,人們被迫妥協於那個要求在所有Unicode字串的前面先行儲存FE FF這兩個位元組的詭異約定,這兩個位元組就是Unicode的位元組順序標記(BOM, Byte Order Mark);如果BOM的高位位元組與低位位元組互換,則形如FF FE,這時讀取字串的人就知道此後每兩個位元組都要做一次調換。 籲! 不過,桀驁不馴的那些Unicode字串並非個個都會在前面乖乖加上BOM。

12. HE=high endian=大頭在前,LE=low endian=小頭在前,這源於雞蛋先敲大頭還是先敲小頭的典故,下文解釋了這兩種的區別,即,HE模式下的位元組序標記為FF FE,LE模式下的位元組序標記為FE FF——譯者草注

enter image description here

暫時看來,這夠應付一陣子了,不過程式設計師們又開始抱怨了。 “看看那些零!”他們會這樣唸叨,因為他們是美國人,他們只看英文,他們那個小得可憐的字符集甚至不會用到大於U+00FF的任何碼點。 還有一個原因,他們是自由散漫的加州嬉皮士,他們習慣於厲行節約(以及冷嘲熱諷)。 如果他們是德州佬,才不在乎鯨吞牛飲兩倍的位元組容量呢。13 不過,那些加州怪胎可無法接受放任字串儲存空間翻番的主意,更何況現如今所有這些吊兒郎當的文件都已經使用了各種ANSI字符集及DBCS字符集,又該找誰來全盤轉換它們呢? 難道去找新聞部14 僅僅因為這一個原因,大多數人都決定先擱置Unicode幾年再說;與此同時,情況變得越來越糟。

13. 這涉及到美國的地域文化,加州=加利福尼亞州=特點是嬉皮挑戰權威,德州=德克薩斯州=特點是地廣人稀——譯者草注
14. 這應該是作者調侃新聞部的一個梗——譯者草注

於是,人們發明出UTF-8這樣的天才概念。15 UTF-8是另外一套儲存字串Unicode碼點資訊的機制,它在記憶體中使用字長為8個位元的位元組儲存那些神奇的U+數字。 在UTF-8編碼機制中,碼值在0至127之間的所有碼點均儲存為一個位元組。 只有儲存碼值大於或等於128的碼點時,才會用到第二個位元組、第三個位元組、……事實上最多可使用六個位元組。

15. UTF=unicode transform format, UCS=Universal Character Set,可能需說明Unicode與UCS的淵源——譯者草注

enter image description here16

16. 細心的讀者會發現,除第一行外,每一行中二進位制位元組串的可用碼點數都要大於最大碼點與最小碼點的差值,這個差值恰好為最小碼點所表示的數。碼點對二進位制串的轉換演算法簡述為,任意碼值均展開為區間內的最大碼點的二進位制位數,將這些二進位制位依次填入第3列二進位制位元組串的v處即可。碼值處於第1行時展開成7個二進位制位,第二行展開成11個,第三行16個,第四行21個,第五行26個,第六行31個——譯者草注
致編輯:這個表格為3列表格,列標題分別為“最小碼點(十六進位制)”、“最大碼點(十六進位制)”、“位元組串(二進位制)”

UTF-8編碼機制具有淨邊際效應——UTF-8編碼下的英文與ASCII編碼下的英文看起來完全一樣,因此美國人甚至感覺不到任何不對勁的地方。 只有剩下來的那些地球人不得不耍猴似的鑽火圈。致編輯:這種措辭是為了體現作者調侃美國人優越意識時的戲謔 具體到“Hello”這個字串,其Unicode碼點為U+0048 U+0065 U+006C U+006C U+006F,使用UTF-8編碼則儲存為48 65 6C 6C 6F,而這(請注意!)恰好等同於該字串使用這個星球上ASCII字符集、ANSI字符集、及各種OEM字符集等進行儲存時的結果。致編輯:作者這時已經偷換了概念,這裡的非Unicode字符集均預設為編碼方式。需要加譯者注嗎? 現在,如果你衝動得要使用注音字元、或希臘字元、或克林貢字元,則請務必使用多個位元組來儲存一個碼點——但美國人永遠看不到這些位元組。 (UTF-8還附送一個利好資訊:那些老氣橫秋的字串處理程式碼只想找一個碼值為0的位元組作為空位終止符,它們傲慢得不屑於一遇到不認識的位元組就截斷字串。)
目前已經講了三種編碼Unicode的方式。 那些傳統的雙位元組儲存法可統稱為UCS-2(2代表這種編碼有兩個位元組)或UTF-16(16代表這種編碼有16個位元);仍需釐清的是,這種UCS-2編碼到底是HE模式、還是LE模式? 第三種是廣受歡迎的新編碼標準UTF-8,它具有堅守崗位的優良品行——不管是幸福邂逅純英文字元,還是不期而遇那種兩耳不聞窗外事、一心只讀ASCII的死板程式,UTF-8都可以正常工作。
其實也存在著一些其他的Unicode編碼方式。 有一種和UTF-8很像的編碼叫做UTF-7,但它承諾高位始終是0,這樣即便那種嚴厲刻板的認為7個位元就“足夠了,謝謝”的極權主義郵件系統,也不得不讓Unicode安全過境、毫髮無傷。 還有一種UCS-4編碼,這裡每個碼點都會儲存為4個位元組,它的好處在於所有碼點在儲存後的位元組數都相同,而糟糕的是:天啦!即使蠻荒魯鈍的德州佬也不至於膽大妄為得浪費如此之多的記憶體。致編輯:給(德州佬)加了定語,待商榷
事實上,現在你思考事物的方式已蛻變成:那些柏拉圖理想國中的字元都表示成了Unicode碼點,而這些Unicode碼點在編碼時也可以使用任何一種因循守舊的編碼機制!致編輯:(因循守舊)是為了不與下文重複,且流露出派系延承之意 舉個例子,“Hello”的Unicode碼點串為(U+0048 U+0065 U+006C U+006C U+006F),編碼時可以使用ASCII編碼、或老式希臘語編碼(OEM內碼表737)、或新式希伯來語編碼(ANSI內碼表1255)、或迄今所發明的上百種編碼之一,但要記住一點17 其中某些字元可能不得而見! 對於試圖呈現的Unicode碼點,如果在嘗試用來表示它的編碼機制中找不到對應的字元,則通常會返回一個小小的問號:?致編輯:這個問號應為英文半形符號 或者,如果你人品夠好,會得到一個小盒子:�。18 你得到了什麼?是“?”還是“�”?

17. 簡述ASCII編碼、OEM編碼、ANSI編碼、Unicode編碼的前世今生——譯者草注
18. 有時?有時�的原因——譯者草注

這個世界上有上百種傳統編碼機制,它們只能正確儲存某些碼點,並把所有其他碼點都變成問號。 其中一些常用的英文編碼如Windows-1252(Windows 9x平臺上用於西歐語系的標準編碼)和ISO-8859-1,後者又稱作Latin-1(同樣適用於任意的西歐語言)。 但如果嘗試用這些編碼儲存俄語字母或希伯來語字母,則會得到一堆問號。 UTF-7、UTF-8、UTF-16及UTF-32都有著上好的品質,它們都能正確儲存任意碼點。

一個最重要的關於編碼的真理

即使完全不記得剛剛講過的任何東西,也請你記住一條最最重要的真理。 不知道其編碼的字串沒有任何意義! 你再也不能把頭埋在沙堆裡,假裝“純”文字就是一些ASCII字元。

- 根本就沒有“純文字”這種東西!

假設有一個字串,不管它是在記憶體中、在檔案中還是在郵件訊息中,都必須知道它的編碼是什麼,否則就不能正確地解讀它,也不能正確地將其顯示給使用者。
幾乎所有諸如“我的網站看上去全是亂碼”、“她讀不了我發的帶注音符號的郵件”之類的愚蠢問題都可以歸結於某個稚氣可掬的程式設計師,他不瞭解這樣一個簡單的事實,即,如果不說明具體的字串到底是使用UTF-8編碼、或ASCII編碼、或ISO 8859-1編碼(即Latin 1編碼)、或Windows 1252編碼(西歐語系編碼),就無法正確地顯示它,甚至找不到這個字串的結束位置。 我們有上百種編碼,而對於碼值大於127的那些碼點,一子落錯,滿盤皆輸。致編輯:(all bets are off)是個俚語,查到的意思是(an expression meaning a situation in which one factor alone can change or cancel out everything)
如何才能獲得字串的編碼資訊呢? 嗯,確實有一些標準的套路可循。 如果是郵件訊息,應該在訊息體頭部有這樣一個字串:

Content-Type: text/plain; charset="UTF-8"

如果是網頁,最初的策略是讓網站伺服器隨網頁本身返回一個類似於Content-Type的HTTP報文頭——但這個報文頭不在HTML程式碼之內,而是在傳送網頁之前作為眾多應答報文頭之一先行傳送。
這會帶來一些問題。 假設有一個大型網站伺服器,上面有很多站點和數以百計的網頁,這些網頁來自於很多人,他們操持著很多不同的語言,所有這些人都使用了其Microsoft FrontPage付費拷貝在審時度勢後自動生成的編碼。 網站伺服器自己其實並不知道撰寫各個檔案時所用的編碼,因此它也就無法傳送Content-Type報文頭。
如果能把網頁檔案的Content-Type報文頭直接放在HTML檔案內,那就方便了——當然這要用到某種特殊的標籤。 嗯,這肯定會讓純化論者抓狂……你怎麼能在不知道HTML檔案編碼的情況下就讀取它呢?! 幸運的是,對於碼值在32至127之間的字元,幾乎所有的通用編碼都表現得一模一樣,因此你可以一直往下讀完這段HTML程式碼,而不用擔心遇到什麼奇趣橫生的字元:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

不過,這裡的<meta>標籤要作為<head>程式碼段中的第一個標籤,這是因為網頁瀏覽器一看到這個標籤,就會停止解析網頁,並使用這裡指定的編碼從頭重新解析整個網頁。
如果網頁瀏覽器在HTTP報文頭及<meta>標籤中都沒有找到Content-Type,又該怎麼辦? Internet Explorer對此做了一件真的非常有意思的事情。 它試圖體察上意,其猜度的理論根據是:不管使用哪種語言、哪種編碼,對於各種語言的代表性編碼而言,各種位元組在代表性文字中的出現頻率應該有跡可尋。 這是因為,各種使用8個位元的老式內碼表習慣於將本國字母放在128至255之間的不同位置,又因為所有的人類語言關於其字母使用頻率都有著各不相同的特徵分佈規律,所以這確實有可能獲得成功。 這真的很怪異,但它碰巧猜對的機率好像確實很高,高到讓那些從不知Content-Type報文頭為何物的稚拙網頁寫手可以沾沾自喜地在自己的網頁瀏覽器中欣賞自己做的網頁,嗯,看來很不錯哦……直到有一天,他們寫的東西恰好沒有服從其母語的字母使用頻率分佈規律,而Internet Explorer則認定他們寫的是韓語並做了對應的顯示……我想,這恰好證明了Postel關於“寬進窄出”的那個著名法則——實話實說——並不是一條好的工程原則。致編輯:暫時沒有找到這個Postel法則的出處 不管怎樣,對那個可憐的網站瀏覽者來說,明明是保加利亞語編寫的網站卻顯示成了韓語(甚至是前言不搭後語的韓語),他該怎麼辦呢? 他使用【檢視 | 編碼】選單,挨個試上一堆各不相同的編碼(光是西歐語系就有數十種編碼可用),直到能撥開雲霧見月明。 這裡已經假定他知道這麼做——而大多數人恰恰並不知道這樣做!

enter image description here

鄙人的公司曾經發布過一款網站管理軟體CityDesk,我們決定在其最新版本的內部全部採用UCS-2(雙位元組)版本的Unicode,這個版本的Unicode正好也是Visual Basic、COM及Windows NT/2000/XP等使用的標配字串型別。19 如果是C++程式碼,宣告字串時只要用wchar_t(wchar=wide char,意為“寬字元”)取代char即可;str系列函式也要替換為相應的wcs系列函式(例如,使用wcscatwcslen分別替換掉strcatstrlen)。 如果是C程式碼,要想建立一個UCS-2型別的文字字串,只要在字串前面加上一個“L”即可,形如:

19. 作者的廣告時間及CityDesk簡介——譯者草注

L"Hello"

CityDesk釋出網頁時,會先把網頁轉換為UTF-8編碼——後者多年來一直受到各種網頁瀏覽器的良好支援。 鄙人的部落格網站目前共有29種語言版本,它們全都是這樣編碼的;迄今為止,我還沒有聽到過有誰抱怨說看不了那些網頁。20

20. 作者網站的多語言版本——譯者草注

這篇文章已經寫得夠長了,而且我也不可能在這裡涵蓋字元編碼和Unicode的方方面面,但我確實希望,既然你已經讀到了這裡,就應該儲備了足夠的知識以迴歸程式設計戰場了——只是拜託別再用水蛭和咒語了,請使用抗生素這種新式武器吧。下面就看你的了!

相關文章