http://www.freebuf.com/articles/web/25623.htmlhtml
若是你是一個生活在2003年的程序員,卻不瞭解字符、字符集、編碼和Unicode這些基礎知識。那你可要當心了,要是被我抓到你,我會讓你在潛水艇裏剝六個月洋蔥來懲罰你。程序員
這個邪惡的恐嚇是Joel Spolsky在十年前首次發出的。不幸的是,不少人認爲他只是在開玩笑,所以,如今仍有許多人不能徹底理解Unicode,以及Unicode, UTF-8, UTF-16之間的區別。這就是我寫這篇文章的緣由。web
言歸正傳,設想在一個晴朗的下午,你收到一封電子郵件,它來自一個你高中以後就失去聯繫的朋友,並帶有一個txt格式(也稱爲純文本格式)的附件。這個附件包含下面這樣一串二進制bits:vim
0100100001000101010011000100110001001111瀏覽器
Email的正文是空的,這使它更加神祕。在你啓動經常使用的文本編輯器打開這個附件以前,你有沒有想過,文本編輯器是怎麼將二進制形式翻譯成字符的?這其中有兩個關鍵問題:架構
1,字節是怎樣分組的?(例如1個字節的字符和2個字節的字符)編輯器
2,一個或多個字節是怎麼映射到字符上的?ide
這些問題的答案就在這篇文檔(Character Encoding)中,大體說來,編碼定義了兩件事:wordpress
1,字節是怎麼分組的,如8 bits或16 bits一組,這也被稱做編碼單元。字體
2,編碼單元和字符之間的映射關係。例如,在ASCII碼中,十進制65映射到字母A上
字符編碼和字符集之間有微小的區別。不過一般它和你無關,除非你在設計一個底層的庫。
ASCII碼是上個世紀最流行的編碼體系之一,至少在西方是這樣。下圖顯示了ASCII碼中編碼單元是怎麼映射到字符上的。
有一個即便在經驗豐富的程序員中也很是常見的誤解就是,純文本使用ASCII碼而且每一個字符都是8 bits。
事實是,沒有這樣的「純文本」。若是在內存或者硬盤中有一個你不知道編碼的字符串,那你就沒法翻譯或者顯示它。這絕對沒有第二條路可選。
那麼當你剛剛收到的附件沒有指定編碼格式的時候,計算機會如何翻譯它呢?這是否意味着你就永遠也讀不到失去聯繫的老朋友想跟你說的話了呢?在咱們找到答案以前,咱們首先回到那個年代—那個用錢能買到的最大硬盤是29MB的時代。
一,歷史回顧
好久之前,計算機制造商有本身的表示字符的方式。他們並不須要擔憂如何和其它計算機交流,並提出了各自的方式來將字形渲染到屏幕上。隨着計算機愈來愈流行,廠商之間的競爭更加激烈,在不一樣的計算機體系間轉換數據變得十分蛋疼,人們厭煩了這種自定義形成的混亂。
最終,計算機制造商一塊兒制定了一個標準的方法來描述字符。他們定義使用一個字節的低7位來表示字符,而且製做瞭如上圖所示的對照表來映射七個比特的值到一個字符上。例如,字母A是65,c是99,~是126等等, ASCII碼就這樣誕生了。原始的ASCII標準定義了從0到127 的字符,這樣正好能用七個比特表示。不過好景不長。。。
爲何選擇了7個比特而不是8個來表示一個字符呢?我並不關心。可是一個字節是8個比特,這意味着1個比特並無被使用,也就是從128到255的編碼並無被制定ASCII標準的人所規定,這些美國人對世界的其它地方一無所知甚至徹底不關心。
其它國家的人趁這個機會開始使用128到255範圍內的編碼來表達本身語言中的字符。例如,144在阿拉伯人的ASCII碼中是گ,而在俄羅斯的ASCII碼中是ђ。即便在美國,對於未使用區域也有各類各樣的利用。IBM PC就出現了「OEM 字體」或」擴展ASCII碼」,爲用戶提供漂亮的圖形文字來繪製文本框並支持一些歐洲字符,例如英鎊(£)符號。
用IBM擴展字符集繪製的很酷的DOS啓動畫面
再強調一遍,ASCII碼的問題在於儘管全部人都在0-127號字符的使用上達成了一致,但對於128-255號字符卻有不少不少不一樣的解釋。你必須告訴計算機使用哪一種風格的ASCII碼才能正確顯示128-255號的字符。
這對於北美人和不列顛羣島的人來講不算什麼問題,由於不管使用哪一種風格的ASCII碼,拉丁字母的顯示都是同樣的。英國人還須要面對的問題是原始的ASCII碼中不包含英鎊符號,可是這個已經可有可無了。
與此同時,在亞洲有更讓人頭疼的問題。亞洲語言有更多的字符和字形須要被存儲,一個字節已經不夠用了。因此他們開始使用兩個字節來存儲字符,這被稱做DBCS(雙字節編碼方案)。在DBCS中,字符串操做變得很蛋疼,你應該怎麼作str++或str–?
這些問題成爲了系統開發者的噩夢。例如,MS DOS必須支持全部風格的ASCII碼,由於他們想把軟件賣到其餘國家去。他們提出了「內碼錶」這一律念。例如,你須要告訴DOS(經過使用」chcp」命令)你想使用保加利亞語的內碼錶,它才能顯示保加利亞字母。內碼錶的更換會應用到整個系統。這對使用多種語言工做的人來講是一個問題,由於他們必須頻繁的在幾個內碼錶之間來回切換。
儘管內碼錶是一個好主意,可是它不是一個簡潔的解決方案,它只是一個hack技術或者說是簡單的修正來讓編碼系統能夠工做。
二,進入Unicode的世界
最終,美國人意識到他們應該提出一種標準方案來展現世界上全部語言中的全部字符,以便緩解程序員的痛苦和避免字符編碼引起的第三次世界大戰。出於這個目的,Unicode誕生了。
Unicode背後的想法很是簡單,然而卻被廣泛的誤解了。Unicode就像一個電話本,標記着字符和數字之間的映射關係。Joel稱之爲「神奇數字」,由於它們多是隨機指定的,並且不會給出任何解釋。官方術語是碼位(Code Point),老是用U+開頭。理論上每種語言中的每種字符都被Unicode協會指定了一個神奇數字。例如希伯來文中的第一個字母א,是U+2135,字母A是U+0061。
Unicode並不涉及字符是怎麼在字節中表示的,它僅僅指定了字符對應的數字,僅此而已。
關於Unicode的其它誤解包括:Unicode支持的字符上限是65536個,Unicode字符必須佔兩個字節。告訴你這些的人應該去換換腦子了。
記住,Unicode只是一個用來映射字符和數字的標準。它對支持字符的數量沒有限制,也不要求字符必須佔兩個、三個或者其它任意數量的字節。
Unicode字符是怎樣被編碼成內存中的字節這是另外的話題,它是被UTF(Unicode Transformation Formats)定義的。
Unicode編碼
兩個最流行的Unicode編碼方案是UTF-8和UTF-16。讓咱們看看它們的細節
UTF-8
UTF-8是一個很是驚豔的概念,它漂亮的實現了對ASCII碼的向後兼容,以保證Unicode能夠被大衆接受。發明它的人至少應該得個諾貝爾和平獎。
在UTF-8中,0-127號的字符用1個字節來表示,使用和US-ASCII相同的編碼。這意味着1980年代寫的文檔用UTF-8打開一點問題都沒有。只有128號及以上的字符才用2個,3個或者4個字節來表示。所以,UTF-8被稱做可變長度編碼。
回到文章開始的問題,來自你老朋友的附件的字節流以下:
0100100001000101010011000100110001001111
這個字節流在ASCII和UTF-8中表示相同的字符:HELLO
UTF-16
另外一個流行的可變長度編碼方案是UTF-16,它使用2個或者4個字節來存儲字符。然而,人們逐漸意識到UTF-16可能會浪費存儲空間,但那是另外一個話題了。
低字節序(Little Endian)和高字節序(Big Endian)
Endian讀做End-ian或者Indian。這個術語的起源能夠追溯到格列佛遊記。(小說中,小人國爲水煮蛋應該從大的一端(Big-End)剝開仍是小的一端(Little-End)剝開而爭論,爭論的雙方分別被稱爲「大端派」和「小端派」。)
低字節序和高字節序只是一個關於在內存中存儲和讀取一段字節(被稱做words)的約定。這意味着當你讓計算機用UTF-16把字母A(佔兩個字節)存在內存中時,使用哪一種字節序方案決定了你把第一個字節放在第二個字節的前面仍是後面。這麼說有點不太容易懂,讓咱們來看一個例子:當你使用UTF-16存下來自你朋友的附件時,在不一樣的系統中它的後半部分多是這樣的:
00 68 00 65 00 6C 00 6C 00 6F(高字節序,高位字節被存在前面)
68 00 65 00 6C 00 6C 00 6F 00(低字節序,低位字節被存在前面)
字節序方案只是一個微處理器架構設計者的偏好問題,例如,Intel使用低字節序,Motorola使用高字節序。
字節順序標記(BOM)
若是你常常要在高低字節序的系統間轉換文檔,而且但願區分字節序,還有一種奇怪的約定,被稱做BOM。BOM是一個設計得很巧妙的字符,用來放在文檔的開頭告訴閱讀器該文檔的字節序。在UTF-16中,它是經過在第一個字節放置FE FF來實現的。在不一樣字節序的文檔中,它會被顯示成FF FE或者FE FF,清楚的把這篇文檔的字節序告訴瞭解釋器。
BOM儘管頗有用,但並非很簡潔,由於還有一個相似的概念,稱做「魔術字」(Magic Byte),不少年來一直被用來代表文件的格式。BOM和魔術字間的關係一直沒有被清楚的定義過,所以有的解釋器會搞混它們。
恭喜你讀到這裏,你必定是一個頗有耐心的讀者。
還記得文章開頭的問題嗎,既然沒有「純文本」文件這回事,那你的文本編輯器和瀏覽器爲何每次都能正確的顯示內容呢?答案是,那些軟件欺騙了你,這也是爲何那麼多人對編碼一無所知。當軟件不能肯定編碼的時候,它會猜想。大部分時候,它會猜想是不是涵蓋了ASCII碼的UTF-8,仍是ISO-8859-1,也有可能猜其餘能想到的任意字符集。由於英文中使用的拉丁字母表在幾乎全部的字符集中都能顯示,包括UTF-8,因此即便編碼猜錯了,英文字母看起來也是正確的。
可是,若是你在瀏覽網頁時看到�符號,這意味着這個網頁的編碼不是你的瀏覽器猜想的那個。這時你能夠點開瀏覽器的查看->字符編碼菜單來嘗試不一樣的編碼。
三,總結
若是你沒時間讀整篇文章或者你僅僅是略讀了一下前面的內容。那請你確保你能理解下面的幾條:
1,這個世界上歷來沒有純文本這回事,若是你想讀出一個字符串,你必須知道它的編碼。
2,Unicode是一個簡單的標準,用來把字符映射到數字上。Unicode協會的人會幫你處理全部幕後的問題,包括爲新字符指定編碼。
3,Unicode並不告訴你字符是怎麼編碼成字節的。這是被編碼方案決定的,經過UTF來指定。
還有最重要的:
4,永遠記得經過Content-Type或者meta charset標籤來顯式指定你的文檔的編碼。這樣瀏覽器就不須要猜想你使用的編碼了,他們會準確的使用你指定的編碼來渲染文檔。
來源聲明:本文來自於10K-LOC的博文《Unicode isn’t harmful for health – Unicode Myths debunked and encodings demystified》,由IDF實驗室封暢翻譯。
(全文完)