Unicode



理解Unicode,以及Unicode, UTF-8, UTF-16之間的區別



設想在一個晴朗的下午,你收到一封電子郵件,它來自一個你高中之後就失去聯繫的朋友,並帶有一個txt格式(也稱爲純文本格式)的附件。這個附件包含下面這樣一串二進制bits:

0100100001000101010011000100110001001111

Email的正文是空的,這使它更加神祕。在你啓動常用的文本編輯器打開這個附件之前,你有沒有想過,文本編輯器是怎麼將二進制形式翻譯成字符的?這其中有兩個關鍵問題:

1,字節是怎樣分組的?(例如1個字節的字符和2個字節的字符)

2,一個或多個字節是怎麼映射到字符上的?

編碼定義了兩件事:

1,字節是怎麼分組的,如8 bits或16 bits一組,這也被稱作編碼單元。

2,編碼單元和字符之間的映射關係。例如,在ASCII碼中,十進制65映射到字母A上

字符編碼和字符集之間有微小的區別。不過通常它和你無關,除非你在設計一個底層的庫。

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碼”,爲用戶提供漂亮的圖形文字來繪製文本框並支持一些歐洲字符,例如英鎊(£)符號。

lpcec9qu3zw3u5uldh_bnawejbesszsmhiv2jm-pyemhgj2ky0vkvn0ousiaqvmv4kmsg_gmhhlhwcbem-uem4wcxkm5hcungr3r7bibhm1ievimyks2cxidbg

用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,所以即使編碼猜錯了,英文字母看起來也是正確的。

但是,如果你在瀏覽網頁時看到�符號,這意味着這個網頁的編碼不是你的瀏覽器猜測的那個。這時你可以點開瀏覽器的查看->字符編碼菜單來嘗試不同的編碼。



那麼UTF-8怎麼知道具體用幾位表示呢?在碼字的最開始幾位表達了這個位數信息。

Unicode/UCS-4
bit數
UTF-8
byte數
備註
0000 ~
007F
0~7
0XXX XXXX
1
 
0080 ~
07FF
8~11
110X XXXX
10XX XXXX
2
 
0800 ~
FFFF
12~16
1110XXXX
10XX XXXX
10XX XXXX
3
基本定義範圍:0~FFFF
1 0000 ~
1F FFFF
17~21
1111 0XXX
10XX XXXX
10XX XXXX
10XX XXXX
4
Unicode6.1定義範圍:0~10 FFFF
20 0000 ~
3FF FFFF
22~26
1111 10XX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
5
說明:此非unicode編碼範圍,屬於UCS-4 編碼
早期的規範UTF-8可以到達6字節序列,可以覆蓋到31位元(通用字符集原來的極限)。儘管如此,2003年11月UTF-8 被 RFC 3629 重新規範,只能使用原來Unicode定義的區域, U+0000到U+10FFFF。根據規範,這些字節值將無法出現在合法 UTF-8序列中
400 0000 ~
7FFF FFFF
27~31
1111 110X
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
6






三,總結

如果你沒時間讀整篇文章或者你僅僅是略讀了一下前面的內容。那請你確保你能理解下面的幾條:

1,這個世界上從來沒有純文本這回事,如果你想讀出一個字符串,你必須知道它的編碼。

2,Unicode是一個簡單的標準,用來把字符映射到數字上。Unicode協會的人會幫你處理所有幕後的問題,包括爲新字符指定編碼。

3,Unicode並不告訴你字符是怎麼編碼成字節的。這是被編碼方案決定的,通過UTF來指定。


發佈了27 篇原創文章 · 獲贊 52 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章