【翻譯】每一個程序員都須要瞭解的Unicode和字符集知識

原文: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)html

翻譯:Sevenschan 歸檔地址git

PS: 歷來沒有翻譯過一篇文章的做者這麼喜歡講段子。。翻譯起來很費勁。。並且真正有用的乾貨說實話很少。。有空的時候再把裏面的乾貨抽出來寫一篇讀書筆記吧。。程序員

有對那個神祕的Content-Type標籤產生過好奇嗎?當把它放在HTML裏面以後會發生什麼你不知道的事呢?github

你試過收到一封來自保加利亞的朋友的email,可是都是一堆「???? ????????? ????????? ???」嗎?瀏覽器

我很傷心的發現有不少的程序員都不能徹底的瞭解字符集、編碼、Unicode等等的神祕世界。幾年前,一個FogBUGZ的測試人員想知道是否能夠用日語來處理收到的電子郵件。日語?他們有收到日語的郵件(是否是哪一個不可言喻的網站的驗證郵件)?人家很傻很天真一點都不懂喲。當我仔細看看咱們用來解析MME郵件信息的商業ActiveX控件的時候,咱們發現它對於字符集的處理是錯得離譜,因此咱們不得不寫一段英雄般的代碼來逆轉錯誤而且重作一遍正確的。當我查看另外的商業庫的時候,mmp,一個樣,字符都被搞得支離破碎。我跟這些庫的做者反應,獲得的回答是他們「沒有任何辦法」。服務器

同時,我發現世界第一的語言PHP幾乎徹底無視字符編碼問題,由於它使用8位字符,這樣的設計使它幾乎沒有可能開發優秀的國際化應用。我想,足夠就是最好了嗎。測試

因此我發佈了一個公告:若是你是2003年之後的程序員,但你不知道關於字符的基礎知識,不知道字符集,不知道編碼,不知道unicode的話,老子會捉住你,而後把你晾在遊艇上半年。我發誓我會這樣作的。字體

更重要的一點事: 它其實一點都不難網站

在這篇文章裏,我會把每一個工做的程序員都須要知道的點都塞進你的腦殼裏。要你知道 「plain text = ascii = 8位字符」 不只是錯,是大錯特錯。你就像那些不相信細菌的醫生同樣。因此,請你求求你在看完這篇文章之前,不要再寫一句代碼了。ui

在我開始以前【譯者注:這篇文章的前戲真長。。】,我應該提醒你若是你是那些罕見的熟悉國際化的人,你會發現我談論的點都是很淺顯的。我是真的只是嘗試去設置最低界線,來幫助各位能夠編寫更好的代碼。還有一點,字符集只是在系統國際化中的一個小部分,我也只能在一篇文章裏面談論一個小部分,反正老子說了算。

歷史觀

最簡單瞭解這些東西的方法就是看看它們的時間線。

你可能會覺得我要講老掉牙的字符集,例如EBCDIC。嘿嘿,老子怎麼會被大家凡人猜到。 EBCDIC已經跟大家的生活毫無關係了,咱們也不須要講到那麼遠的歷史。

回到半年後,當Unix出世之後而且K&R還在寫C語言的時候,全部東西都很簡單。EBCDIC就是使用的方案。惟一有關係的老的好的字符就是英文字母,咱們把它成爲ASCII,容許它們使用32到127之間的每一個數字來表示每一個字符。 空格是32,大寫字母「A」是65,等等。這樣能夠很方便的保存在7位(bit)裏。現代電腦大部分都是使用8位字節,所以你不只能夠存儲每個ASCII字符,你還有不少位置去作壞事,你能夠存一些稀奇古怪的字符。少於32位的代碼稱爲不可打印代碼。

一切都很好,假設你的母語是英語。由於咱們用8位去存儲字符,不少人就會都想到:「嘿嘿,咱們能夠用128-255位置來存儲咱們本身的符號。」image 麻煩的是,太多人在同一時間有這個想法了,對於這些富餘的位置各有各想法各有各精彩。IBM的電腦提供了一堆叫OEM字符的東西,用來提供歐洲的重音字符和一堆線條。。橫的、豎的、垂直右邊有個小鈴鐺的等等。你能夠用這些線條字符在屏幕上去堆一個精美的盒子出來,實際上你依然能夠在使用8088電腦的乾衣機上看到它們的身影。實際上,隨着美國之外的人們開始買屬於本身的電腦之後,不一樣的128個字母都被賦予了他們各人的目的。例如,在一些電腦上面字符編碼130會顯示é,可是在以色列的電腦上它就會顯示 ג。因此當美國人想發送他們的résumés 到以色列的話,就會變成了rגsumגs。在不少案例中,例如俄羅斯,他們對這128位以後的字符編碼的使用有着大量不一樣的想法,因此你很難去可靠的交換俄羅斯的文件。

最後,OEM決定編纂ANSI標準。在ANSI標準裏面,全部人都贊成如何使用128位如下的位,這幾乎與ASCII相同,可是從128位開始到最後一個編碼位的使用就大有不一樣。這些不一樣的系統被稱爲編碼頁(Code Page)。例如在以色列的DOS裏面使用的編碼頁被稱爲862,希臘用戶用的稱爲737。他們使用的128位如下都是相同的,可是從128位之後開始就充斥了各類有趣的字母了。MS-DOS國家版本里面有幾十個這些編碼頁,能夠處理英語到冰島語,甚至還能夠在同一臺電腦裏面處理世界語和加利亞語!屌爆!可是,希伯來語和希臘語在同一臺計算機上是徹底不可能的,除非你編寫本身的自定義程序用來顯示全部使用位圖的圖形,由於希伯來語和希臘語須要不一樣的編碼頁與高數字。

與此同時,亞洲有着成千上百個字符,這些遠遠不能放進8位裏面。這同樣會經過稱爲DBCS的五花八門的系統來解決,「雙字節字符集」裏面存着字符一些佔用了一個字節,一些佔用了兩個字節。這樣在一個字符串中很容易能夠向前移,可是很難向後移。程序員最好不要使用s++和s-去向前或者向後移字符串,鼓勵使用例如window裏面AnsiNext和AnsiPrev的方法來避免混亂。

可是,只要歷來不把一個字符串移動到另外一臺電腦的話,或者不講另外一種語言的話,把一個字節看成一個字符或者把一個字符看成是八位是可行的。但固然,隨着互聯網發展,字符串從一臺電腦發送到另外一臺電腦變得至關廣泛。就在這個時候,Unicode出現了。

Unicode

Unicode致力於創造一個能夠適應全部地球上操做系統的單一字符集。有些人誤解Unicode只是一個簡單的16位編碼,每一個字符佔用16位因此只能保存65536個字符。這!一點!都不對!這是一個屬於Unicode的神話,你不懂別bb。

實際上,關於字符,Unicode有本身的一套獨到的思考方式,若是你不能瞭解它的想法,你會感覺不到它的好。

如今,咱們來假設一個字母映射到一些位中,你能夠把它存在硬盤或者內存中。

A -> 0100 0001

在Unicode中,一個字母映射會被映射到一個稱爲 編碼點 [0]的東西上,固然它只是一個概念。如何將 編碼點 在硬盤或者內存中表現出來纔是完整的故事。

在Unicode中,字母A只是一個柏拉圖式的念想,只是漂浮在天堂上:

A

這個柏拉圖式的A跟B不同,跟a也不同,可是跟A 和 AA 都同樣。不一樣字體之間的字符A 都是表明同一個A,可是不一樣於小寫 「a」,看起來無可非議,可是在一些語言裏面弄清楚一個字母是有不少爭議的。德國的字母 ß 是真實存在的字母仍是隻是一種特殊的寫做方式?若是一個字母在單詞裏面最後一筆發生了變化,這是一個新的字母嗎?Hebrew說是的,Arabic說不是。不管如何,在Unicode聯盟裏面的那堆聰明人通過大量的討論辯論,這些問題已經解決了。

每一個柏拉圖式的字母會由Unicode聯盟經過每一個字母表來賦予一個魔術代碼給它們,它是這個樣子的:U+0639 。 這個魔術代碼就是 編碼點 。 編碼點裏面的 U+ 表明 「Unicode」 ,後面的數字是十六進制數。 U+0639 是阿拉伯的字母 Ain。 英語的字母A則是 U+0041 。你能夠經過 Windows 2000/xp 裏面的 charmap 來查詢它們,或者登錄Unicode官網查詢。

Unicode能夠定義的字母數量並無真正的限制,實際上它們超過了65,536,因此不是每一個unicode字母均可以被壓縮成兩個字節,這只是一個神話。

好的,咱們如今來個例子:

Hello

在Unicode裏面,對應的五個編碼點是:

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

反正就是一堆編碼點數字什麼的了。談到這裏,咱們尚未提到如何把這些編碼保存在內存裏面和如何在一封電子郵件裏面重現。

Encodings (編碼)

是時候讓 編碼 出場啦。 Unicode編碼最先的想法就是來源於那個兩個字節的神話,只須要將這些數字分別存在兩個字節裏。就變成這樣:

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

是吧?好像不太快吧?不能夠是這樣嗎?

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

在技術上,我相信能夠作到的。事實上,每一個實現者都但願以高地址和低地址的模式【譯者注:這裏提到的能夠去看一下字節對齊更好理解】來存儲他們的Unicode編碼點。這樣的話不管是任何CPU來處理,速度都是至關快的。因此人們不得不提出在每一個Unicode字符串的開頭存儲一個FE FF的奇怪約定;這個被稱爲 Unicode Byte Order Mark(BOM),若是你交換了你的高低字節,那麼它將會看起來是 FF FE 這樣,那麼其餘人讀到你的字符串的時候就能夠知道須要把字節交換回來了。 固然了,不是每一個Unicode 字符串開頭都會有BOM標記的。 image

這看起來已經至關好了,當時有些程序員會抱怨。「看看那些 0 !」,他們說。由於美國人不多會用高於 U+00FF 的編碼點。【譯者注:而後這裏一段都是諷刺德州人和加州人。。沒什麼養分,略去。】大意就是這些用慣了ANSI和DBCS字符集的人懶得轉換使用Unicode,而且固定長度的Unicode會致使一些字符會保存大量的0字節,所以Unicode被忽略了不少年。

所以,輝煌的UTF-8被提出了。 UTF-8是使用另外一個系統來存儲你的Unicode編碼點字符串,那些模式U+數字在內存裏使用8位字節,而在UTF-8裏面,每一個0-127的編碼點都被存在了一個獨立的字節裏。只有當編碼點大於等於128的時候纔會只用第二第三個字節,實際上,最高能夠容許使用六個字節。

image

這種作法使得UTF-8看起來就跟ASCII同樣整潔,所以美國佬沒有發現出區別。 只有世界上其餘的人才能跳出思惟圈圈。具體來講,Hello, 編碼點串爲 U+0048 U+0065 U+006C U+006C U+006F,會被保存爲48 65 6C 6C 6F。看啊哇塞!這跟保存在ASCII或者地球上其餘的OEM字符集都同樣啊!如今,你能夠大膽地使用重音字母或者希臘字母或者克林貢字母,即便你不得不使用幾個字節來保存一個編碼點,可是美國佬們永遠都不會注意到。(UTF-8也有一個不錯的屬性,那個想要使用單個0字節做爲空終止符的無效舊字符串處理編碼不會被截斷)。

到目前爲止,我已經講述了三種Unicode編碼。傳統的保存在兩個字節的方法被稱爲UCS-2(由於它有兩個字節)或者UTF-16 (由於它有16位),並且你依然須要區分這是高位UCS-2仍是低位UCS-2。還有很受歡迎的UTF-8。

實際上有一些其餘的Unicode編碼方法。 有一個叫UTF-7,很是像UTF-8,可是它保證高位老是爲零,所以若是你要經過某種惡意的警察郵件系統來傳遞Unicode,UTF-7是足夠的了。還以一種UCS-4,會把每一個編碼點保存到4個字節裏面,優勢是每一個編碼點都能草存到相同大小的字節裏面,可是,節儉的德州人會很在乎這些浪費的。

事實上,你正在思考使用Unicode編碼點來表示柏拉圖式字母,那些Unicode編碼點也能夠用任何老式的編碼方案進行編碼!舉個例子,你能夠對Unicode字符串Hello (U+0048 U+0065 U+006C U+006C U+006F) 進行ASCII編碼,或者古老的OEM希臘語編碼,或者Hebrew ANSI 編碼,或者任何其餘有上百年曆史的編碼方式。你會發現,都會是相同的結果:一些字母不會被顯示出來!它會顯示:? 或者�。

幾百種傳統的編碼方法都只能正確的保存部分編碼點,其餘的編碼點都會顯示問號。一些流行的英文文本編碼是Windows-1252(西歐語言的Windows 9x標準)和ISO-8859-1(也稱爲拉丁語1)(也適用於任何西歐語言)在嘗試保存俄文或者Hebrew字母的時候,都會獲得一大堆問號。UTF 7,8,16和32則再這方面都有着很好的表現。

關於編碼的一個重要的事實

若是你已經忘光了我剛剛解釋的一切,請記住一個事實。只有字符串而不知道它使用的編碼,這是毫無心義的。你不能夠再把頭埋在洞裏,僞裝這個文本使用的是ASCII。

沒有沒有這樣的事情做爲純文本

若是你有一個字符串在內存,在文件或者在電子郵件信息中,你必須知道它使用的編碼不然你不能正確地讀到它。

「個人網站看起來亂七八糟」或者「她不能看懂個人電子郵件當我說了重音拼音」等等這些愚蠢的問題都歸咎於天真的程序員,因爲他根本不知道你的信息所使用的編碼。

咱們能不能找到有用的信息來顯示字符串使用的編碼方式呢?答案是有的,咱們經過標準的方法來達到這個目的。對於電子郵件信息,你應該在表單的頭部有一個字符串。

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

對於網頁來講,最初的想法是網站服務器會返回一個相似 Content-Type 的HTTP頭部信息以及網頁自己 —— 不是HTML自己,做爲在HTML頁面發送以前的響應頭之一。

但這會引發問題。假設你有很大的WEB服務器,擁有大量的頁面和成百上千的頁面是由不一樣語言的人提供的,那麼全部使用的編碼將會由Microsoft FrontPage選擇認爲適合的編碼方法來生成。Web服務器自己不會真正知道每一個文件的編碼方式,所以沒法發送Content-Type頭文件。

若是你可使用某種特殊標籤將HTML文件的Content-Type正確放置在HTML文件自己,那將會很方便。固然,這會讓純粹主義者瘋狂...你怎麼能經過讀HTML文件來肯定什麼編碼?幸運的是,幾乎每一個經常使用的編碼間32到127之間的字符都相同,因此你能夠隨時在HTML頁面上獲得這個,而不用擔憂使用「有趣」的字母:

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

要記住這個meta標籤必須是<head>塊裏面很是靠前定義的標籤,由於當瀏覽器看到這個標籤的時候就會中止頁面的解析而且開始使用你指定的編碼從新開始解析整個頁面。

當瀏覽器找不到定義 Content-Type 的meta 標籤並且http頭也沒有定義的時候怎麼辦?IE瀏覽器一般會作一個有趣的事情:它會嘗試去猜想,基於各類語言的典型編碼中的各類字節出如今典型文本中的頻率,來決定使用什麼語言和編碼【譯者注:IE大哥別亂猜啊。。】。由於各類舊的8位編碼頁每每將其國家字母放在128到255之間的不一樣範圍內,而且由於每種人類語言都有不一樣的字母使用特徵直方圖,這實際上有一個可行的機會。這真的很奇怪,但這看起來的確可行,直到有一天他們寫了一些徹底不能根據字母頻率分佈來確認語言的東西出來,IE斷定它是韓語並顯示出來,證實這不是很好的方法。

這篇文章變得很長,我不可能涵蓋全部關於字符編碼和Unicode的知識,可是我但願若是你已經讀到這裏的話,嘗試去更多的地方去了解Unicode,會讓你更獲益良多。

[0] 編碼點,Code point,不一樣地方也有翻譯成碼點或代碼點。

相關文章
相關標籤/搜索