關于字符集和Unicode的相關知識(2)

作者:Joel Spolsky 來源:轉載 時間:2010-03-15 類別:網頁理論

 

編碼

要存儲,編碼的概念當然就被引入進來。

Unicode最早的編碼想法,就是把每一個碼點(code point)都存儲在兩個字節中,這也就導致了大多數人的誤解。于是Hello就變成了:

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

這樣對嗎?如下如何?

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

技術上說,我相信這樣是可以的。事實上,早期的實現者們的確想把Unicode的碼點(code point)按照大端或小端兩種方式存儲,這樣至少已經有兩種存儲Unicode的方法了。于是人們就必須使用FE FF作為每一個Unicode字符串的開頭,我們稱這個為Unicode Byte Order Mark。如果你互換了你的高位與低位,就變成了FF FE,這樣讀取這個字符串的程序就知道后面字節也需要互換了。可惜,不是每一個Unicode字符串都有字節序標記。

現在,看起來好像問題已經解決了,可是這幫程序員仍在抱怨。"看看這些零!"他們會這樣說,因為他們是美國人,他們只看不會碼點不會超過U+00FF的英文字母。同時他們也是California的嬉皮士,他們想節省一點。如果他們是得克薩斯人,可能他們就不會介意兩倍的字節數。但是這樣California節儉的人卻無法忍受字符串所占空間翻倍。而且現在大堆的文檔使用的是ANSI和DBCS字符集,誰去轉換它們?于是這幫人選擇忽略Unicode,繼續自己的路,這顯然讓事情變得更糟。

于是非常聰明的UTF-8的概念被引入了。UTF-8是另一個系統,用來存儲字符串所對應的Unicode的碼點 (code points)-即那些神奇的U+數字組合,在內存中,而且存儲的最小單元是8比特的字節。在UTF-8中,0-127之間的碼字都使用一個字節來存儲,超過128的碼字使用2,3甚至6個字節來存儲。

關于字符集和Unicode的相關知識

這顯然有非常好的效果,因為英文的文本使用UTF-8存儲的形式完全與ASCII一樣了,所以美國人壓根不會注意到發生了什么變化。舉個例子,Hello -- U+0048 U+0065 U+006C U+006C U+006C U+006F,將會被存儲為48 65 6C 6C 6F,這與ASCII、與ANSI標準、與所有這個星球上的OEM字符集顯然都是一樣的。現在,如果你需要使用希臘字母,你可以用幾個字節來存儲一個碼字,美國人永遠都不會注意到。(干嗎得美國人注意,無語,美國人寫的文章...)

到現在我已經告訴了你三種Unicode的編碼方式,傳統的使用兩個字節存儲的稱之為UCS-2或者UTF-16,而且你必須判斷空間是大端的UCS-2還是小端的UCS-2。新的UTF-8標準顯然更流行,如果你恰巧有專門面向英文的程序,顯然這些程序不需要知道UTF-8的存在依然可以工作地很好。

事實上,還有其它若干對Unicode編碼的方法。比如有個叫UTF-7,和UTF-8差不多,但是保證字節的最高位總是0,這樣如果你的字符不得不經過一些嚴格的郵件系統時(這些系統認為7個比特完全夠用了),就不會有信息丟失。還有一個UCS-4,使用4個字節來存儲每個碼點(code point),好處是每個碼點都使用相同的字節數來存儲,可惜這次就算是得克薩斯人也不愿意了,因為這個方法實在太浪費了。

現在的情況變成了你思考事情時所使用的基本單元--柏拉圖式的字母已經被Unicode的碼點完全表示了。這些碼點也可以完全使用其它舊的編碼體系。比如,你可以把 Hello對應的Unicode碼點串(U+0048 U+0065 U+006C U+006C U+006F)用ASCII、OEM Greek、Hebrew ANSI或其它上百個編碼體系來編碼,不過需要注意一點,有些字母會無法顯示。如果你要表示的Unicode碼點在你使用的編碼體系中壓根沒有對應的字符,那么你可能會得到一個小問號"?",或者得到一個"�"。

許多傳統的編碼體系僅僅能編碼Unicode碼點中的一部分,其余全部會被顯示為問號。比較流行的英文編碼體系有Windows-1252(Windows 9x中的西歐語言標準)和ISO-8859-1,還有aka Latin-1。但是如果想用這些編碼體系來編碼俄語或者希伯來語就只能得到一串問號了。UTF 7,8,16,和32都可以完全正確編碼Unicode中的所有碼點。

關于編碼的唯一事實

如果你完全忘掉了我剛剛解釋過的內容,沒有關系,請記住一點,如果你不知道一個字符串所使用的編碼,這個字符串在你手中也就毫無意義。你不能再把腦袋埋進沙中以為"純文本"就是ASCII。事實上,

根本就不存在所謂的"純文本"。

那么我們如何得知一個字符串所使用的空間是何種編碼呢?對于這個問題已經有了標準的作法。如果是一份電子郵件,你必須在格式的頭部有如下語句:

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

對于一個網頁,傳統的想法是Web服務器會返回一個類似于Content-Type的http頭和Web網頁,注意,這里的字符編碼并不是在HTML中指出,而是在獨立的響應headers中指出。

這帶來了一些問題。假設你擁有一個大的Web服務器,擁有非常多的站點,每個站點都包括數以百計的Web頁面,而寫這些頁面的人可能使用不同的語言,他們在他們自己計算機上的FrontPage等工具中看到頁面正常顯示就提交了上來,顯然,服務器是沒有辦法知道這些文件究竟使用的是何種編碼,當然 Content-Type頭也沒有辦法發送了。

如果可以把Content-Type夾在HTML文件中,那不是會變得非常方便?這個想法會讓純粹論者發瘋,你如何在不知道它的編碼的情況下讀一個HTML文件呢?答案很簡單,因為幾乎所有的編碼在32-127的碼字都做相同的事情,所以不需要使用特殊字符,你可以從HTML文件中獲得你想要的Content-Type。

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

注意,這里的meta標簽必須在head部分第一個出現,一旦瀏覽器看到這個標簽就會馬上停止解析頁面,然后使用這個標簽中給出的編碼從頭開始重新解析整個頁面。

如果瀏覽器在http頭或者meta標簽中都找不到相關的Content-Type信息,那應該怎么辦?Internet Explorer做了一些事情:它試圖猜測出正確的編碼,基于不同語言編碼中典型文本中出現的那些字節的頗率。因為古老的8比特的碼頁(code pages)傾向于把它們的國家編碼放置在128-255碼字的范圍內,而不同的人類語言字母系統中的字母使用頗率對應的直方圖會有不同,所以這個方法可以奏效。雖然很怪異,但對于那些老忘記寫Content-Type的幼稚網頁編寫者而言,這個方法大多數情況下可以讓他們的頁面顯然OK。直到有一天,他們寫的頁面不再滿足"letter-frequency-distribution",Internet Explore覺得這應該是朝鮮語,于是就當朝鮮語來顯示了,結果顯然糟透了。這個頁面的讀者們立刻就遭殃了,一個保加利亞語寫的頁面卻用朝鮮語來顯示,效果會怎樣?于是讀者使用 查看-->編碼 菜單來不停地試啊試,直到他終于試出了正確的編碼,但前提是他知道可以這樣做,事實上大多數人根本不會這樣做。

在我的公司開發的一款Web頁面管理軟件CityDesk的最新版本中,我們決定像Visual Basic、COM和Windows NT/2000/XP所做的那樣,整個過程中使用UCS-2(兩個字節)Unicode。在我們寫的C++代碼中,我們把所有的char類型換成了wchar_t,所有使用str函數的地方,換成了相應的wcs函數(如使用wcscatwcslen來替代strcatstrlen)。如果想在C中創建一個UCS-2的字符串,只需在字符串前面加L即可:L"Hello"

當CityDesk發布頁面的時候,它把所有的頁面都轉換成了UTF-8編碼,而差不多所有的瀏覽器都對UTF-8有不錯的支持。這就是"Joel On Software"(就是作者的首頁)編碼的方式,所以即使它擁有29個語言版本,至今也未聽到有一個人抱怨頁面無法瀏覽。

這篇文章已經有點長了,而且我也沒有辦法告訴你關于字符編碼和Unicode的所有應該了解的知識,但讀到現在我想你已經掌握到基本的概念,回去編程時可以使用抗生素而不是螞蝗和咒語了,這就看做是留給你的作業吧。

12
标签: Unicode

相關文章:

推薦設計

最新文章