<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 四、每一位軟件開發人員必須、絕對要至少具備UNICODE 與字符集知識(沒有任何例外!) 有過這樣的經歷嗎?從保加利亞的朋友那里收到電子郵件,主題卻為 “?????????????????”? 我一直十分沮喪地試圖去揭示到底有多少軟件開發人員沒有真正跟上那個充斥著字符 集、編碼技術、Unicode及其素材的神秘世界的變化速度。數年以前,FogBUGZ[1]的P測試 人員一直想知道自己是否能夠處理日語電子郵件。日語?他們收到過日語電子郵件嗎?我不 知道。 當近距離地審視我們一直用來解析Ml ME電子郵件的商業ActiveX控件時,我發現它確實 在字符集方面做得很差。實際上,我們只得用自己寫的代碼取消它完成的錯誤轉換,然后重 新進行轉換工作。在打量另外一個商業軟件庫時,我們發現它同樣有一個毛病很多的字符代 碼實現形式。我與那個軟件包的開發人員進行了聯系,他好像認為“不能去為它做任何事情”。 與許多程序員一樣,他僅僅希望它會以某種方式滑過去就行了。 不過,它是不會過去的。我發現流行的Web開發工具PHP,幾乎完全忽視了字符編碼方面 的問題[2]而單純地使用8位字符,它不大可能開發出好的國際Web應用程序。我真正感覺到, 夠用是怎么一回事情了。 因此,我要做出一項宣示:如果你是一位21世紀的程序員,卻不知道字符、字符集、編 碼技術與Unicode方面的基本知識,那么要是讓我逮著,我打算通過讓你在潛艇上剝6個月的 洋蔥來懲罰你。我發誓我會這么做。 還有一件事情: 它并不那么難。 在本章中,我將告訴你到底每個真正動手編程的程序員應該知道什么。所有關于“純文 本=ASC 11=字符是8位”之類的看法不僅是錯誤的,而且錯誤得令人絕望。要是你仍然按這樣 的方式去編程,那么你比一位不相信細菌的醫學博士好不了多少。務請在讀完本章之前不要 再寫另外一行代碼。 在實際開始之前,我得提請你注意,如果你不是罕見的幾位知道一些國際化知識的人員 之一,那么你會覺得我整個討論有點兒過于簡單。其實,我不過是在這里試圖做最少的一點 拋磚引玉的工作,以便讓大家都能夠理解后續的知識,并且能夠使寫出的代碼有望在任何語種的文本環境,而不僅僅是不包括重音的英文子集的環境下使用。 我還要提醒大家注意,盡管字符處理僅僅是創建能夠在國際語言環境下使用的軟件所要 完成的很微小的部分,不過我一次只能寫一件事情,因此,今天在這里談到的就是字符集。 ## 歷史情景 理解這個素材最為容易的方式是按年代順序依次推進。你可能覺得我打算在這里談論像 EBCDIC之類的古老的字符集。唔,不會的。EBCDIC與你的生活毫不相干。我們也沒有必要追 溯那么久遠的年代。 在計算機出現后的中期時代,人們開發了UNIX,K&R寫出了《C程序設計語言》,一切都 顯得非常簡單。EBCDIC才剛剛浮出水面。惟一很要緊的字符集就是有效而古老的無重音英語 字母,而人們為這些字符開發了一套稱為ASCII的編碼,它能夠使用32到127之間的數字表示 各個字符[3]。空格的編碼是32,字母A的編碼是65,等等。這種編碼能夠很方便地用七個二 進制位來表示。 由于那個時期生產的大多數計算機使用8位大小的字節,因此用戶不僅可以存放所有可 能的ASCII字符,而且有整整一位空余下來。如果你技藝高超,可以將該位用做自己離奇的 目的:WordSta「中那個發暗的燈泡實際上設置這個高位,以指示一個單詞中的最后一個字母, 同時這也宣示了 WordSta「只能用于英語文本。碼值小于32的代碼稱為非打印字符而作為雜項 字符使用,也就是當做小不點兒來用。這些字符用做控制字符,比如,碼值為7的字符使計 算機發出“嘟嘟”聲,而12對應的字符使當前走一頁打印紙并換入新的一頁。 對于一個說英語的人而言,這一切都是不錯的。 由于字節有多達8位的空間,因此許多人在想:“呀!我們可以把128~255之間的編碼用 做個人的應用目的。”問題在于,同時產生這種想法的人相當多,而且在128~255之間的各 個位置上應該存放什么這一問題上,真是仁者見仁智者見智。IBM-PC有一種OEM字符集,它 提供了一些歐洲語系的重音字符,以及一組畫線字符一一水平線條、豎直線條與右邊有彎折 的水平線條等。你可以使用這些畫線字符在屏幕上畫出好看的方框與線條,只要環境清潔干 燥,繪制的圖形在8088計算機上仍然看得見。 事實上,只要人們開始在美國以外的地方購買計算機,那么各種各樣的不同OEM字符集 都會進入規劃設計行列,并且各人都會根據自己的需要使用高位的128個字符。比如,在一 些PC機器上編碼為130的字符顯示為而在以色列銷售的計算機上它顯示為希伯來語的第三 個字母A。因此,當美國人想把自己的履歷(「gsum6s)發給以色列時,顯示出來的會是r 入sum AS。在使用諸如俄語之類的語種的多種情況下,對高端128個字符可能存在很多不同 的處理思路。如此一來,甚至在同語種的文檔之間就不容易實現互換。 最后,這個人人參與的OEM終于以ANSI標準的形式形成文件。在ANSI標準中,每個人都 認同如何使用低端的128個編碼,這與ASCII相當一致。不過,根據所在國籍的不同,處理編 碼128以上的字符有許多不同的方式。這些不同的系統稱為代碼頁[4]。這樣一來,比如說在 以色列DOS使用稱為862的代碼頁,而希臘用戶使用的代碼頁是737。這兩個代碼頁在128以下 是一樣的,但在128以上則不相同,該代碼段存放的是一些古怪的字母。MS-DOS的國家版本 有幾十個這樣的代碼頁,負責將所有的英語信息轉換成冰島語。它們甚至還擁有幾個“使用 多語種”的代碼頁,從而可以在同一臺計算機上處理世界語與加利西亞語!嗚哇!不過,要 說一下,在同一臺計算機上處理希伯來語與希臘語是完全不可能的事情,除非自己編寫一個 定制程序以顯示使用位映象圖形的任何內容,因為希伯來語與希臘語需要能夠對高端編碼做 出不同解釋的不同代碼頁。 同時,甚至更為令人頭疼的事情正在逐步上演,亞洲國家的字符表有成千上萬個字符, 這樣的字符表是用8位二進制無法表示的。該問題的解決通常有賴于稱為DBCS(double byte character set,雙字節字符集)的繁雜字符系統。這個字符系統將一些字符存儲為一個字 節,而讓另外一些字符占據兩個字節。雖然在字符串中向前移動很容易,但向后移動幾乎不 可能做得很棒。我們不鼓勵程序員使用s++與s–的代碼形式在字符串中前后移動,而是提 倡調用Windows的AnsiNext與AnsiPrev函數,這兩個函數知道如何去應付所有的繁雜局面。 不過,仍然需要指出一點,多數人還是姑且認為一個字節就是一個字符,以及一個字符 就是8個二進制位,并且只要確保不將字符串從一臺計算機移植到另一臺計算機,或者說一 種以上的語言,那么這幾乎總是可以湊合。當然,只要一進入Internet從一臺計算機向另一臺計算機移植字符串就成為家常便飯了,而各種復雜狀況也隨之呈現出來。令人欣慰的是,Unicode隨即問世了。 ## Unicode Unicode勇往直前地創建一種單一字符集,試圖囊括地球上所有合理文字體系,以及諸 如一些Klingon之類的人為書寫體制。一些人錯誤地認為,Unicode就是一種每個字符占用16 個二進制位,從而總共可以表示65 536個可能的字符的16位字符編碼方案。這并不十分正確。 它是關于Unicode的惟一最為流行的神話,因此,要是你也這樣想,大可不必感到難過。 事實上,Unicode在考慮字符方面給出了一種不同的思路。你需要去理解它的這種思考 方式,否則一切都顯得毫無意義。 直到現在,我們一直認定字母映射為磁盤或者內存的某些位: ``` A -> 0100 0001 ``` 在Unicode中,一個字母映射為一種稱做代碼點(code point)的對象,它仍然只是一 個理論上的概念。該代碼點如何在磁盤或者內存中進行表示完全是一種微妙的個體行為。 在Unicode中,字母A是一個柏拉圖式的理想,它只能飄蕩在天國之中: ``` A ``` 這個柏拉圖式的A不同于B,也不同于a,但與A或者或者A卻是一樣的。認為Times New Roman字體的A與Helvetica字體中的是相同的字符,但不同于小寫字母a的想法,看起來并 不會引起很大的爭議,但在某些語言中僅僅指出字母是什么完全可能引起爭議。德語的字母 6是真正的字母,抑或僅僅是ss的一種花樣書寫形式?如果一個字母的形狀在單詞的末尾變 化了,那么它是一個不同的字母嗎?希伯來語系認為是,而阿拉伯語系則說不。不管怎樣, 聰明的Unicode人在最近大約十年里一直在設法處理這個方面的問題,與此相伴的是大量高 度政治化的爭論。不過,你用不著擔心,他們己經把它全部處理妥當了。 Unicode組織為各種字母表中每個理想化的字母分配一個神奇的編號,其書寫形式為 U+0645。這個神奇的編號稱為一個代碼點。U+的含義是“Unicode”,這些數字是十六進制 的。U+0639表疋阿拉伯字母Ain。英語字母A是U+0041。使用Windows 2000/XP的charmap工具 程序或者訪問Un i code網站,可以找到所有的字母及其編碼[5]。 Unicode能夠定義的字母個數其實沒有限制,實際用到的總字符個數己經遠遠超出了65 536的范圍。可見,并不是每個Uni code字母能夠真正擠壓成兩個字節,不過,這終歸還是一 個神話。 好了,我們來看一個字符串: ``` Hello ``` 該字符串在Uni code中對應如下形式的5個代碼點: ``` U+0048 U+0065 U+006C U+006C U+006F ``` 這不過是一簇代碼點。其實,就是一些編號。到此為止,我們還沒有談到如何在內存中 存放這類字符串或者怎樣在電子郵件中表示它們。 [1]這是我們的故障跟蹤產品,見www.fogcreek.com/FogBUGZ。 [2]見ca3.php.net/manual/en/language.types.string.php。 [3]關于ASCII字符的更多信息,參見 www.robelle.com/library/smugbook/ascii.htmlo [4]關于代碼頁的更多信息,參見 www.i18nguy.com/unicode/codepages.html#msftdos〇 [5]見www.unicode.org。 ## 編碼 是給出編碼知識的時候了。 關于Unicode編碼,最為容易同時也因此導致2字節神話出現的想法是:“嗨,讓我們將 這些編號各自用兩個字節存放吧!”于是,”Hello”變成了 `00 48 00 65 00 6C 00 6C 00 6F` 對吧?不要這么性急!可以存為 `48 00 65 00 6C 00 6C 00 6F 00` 嗎? 喏,從技術上講是可以的,我就相信這一點。事實上,早期的實施人員就想以高端或者 低端兩種存放模式在內存中表示Unicode碼。不管特定的計算機在何種模式下運行最快,喔, 也不管是傍晚還是清晨,現在己經有了兩種存放Unicode碼的方式。于是,人們被迫提出在 每個Unicode字符串的開頭存放一個FEFF標記的怪異協定。這個標記稱為Unicode字節順序 標記(Unicode Byte Order Mark)。要是交換了它的高低位字節,該標記就成為FF FE。這 樣一來,閱讀字符串的人就知道必須將字符串的每兩個字節進行一次交換[1]。吁!蒼茫世 界當中不是每個Unicode字符串都會在開頭放一個字節標記。 曾幾何時,這樣的做法好像是足夠好了。但是,程序員卻一直沒有停止抱怨:“瞧瞧所 有那些零!”要知道,作為美國人,他們閱讀的英語文本很少用到U+00FF以上的代碼點。 還有,他們是來自加利福尼亞州的無拘無束的嬉皮士,保守與冷嘲熱諷是他們的天性。如果 他們是得克薩斯人,就不會在乎要狂吃雙份的字節。不過,那些咕咕嘰嘰的加利福尼亞人不 會忍受將字符串占據的存儲空間加倍的想法。而且,不管怎么說,己經存在的一些該死的文 檔都赫然使用著各種各樣的ANSI與DBCS字符集,誰會去將它們全部轉換過來呢?僅僅是這個 原因,大多數人決心在幾年之內對Unicode視而不見。不過,與此同時,情況卻變得尤為糟 糕。 于是乎,光彩照人的UTF-8[2]概念就橫空出世了。UTF-8是另外一種存放字符串的 Unicode代碼點的體系,它在內存中使用8位字節存放那些神奇的U+編號[3]。在UTF-8中,從 0~127的每個代碼點存放在單個字節里面,只是128以上的代碼點才使用2個字節、3個字節, 乃至多達6個字節的空間來進行存儲。 | 最小十六進制 | 最大十六進制 | 二進制字節序列 | | --- | --- | --- | | 00000000 | 0000007F | 0vvvvvvv | | 00000080 | 000007FF | 110vvvvv 10vvvvvv | | 00000800 | 0000FFFF | 1110vvvv 10vvvvvv 10vvvvvv | | 00010000 | 001FFFFF | 11110vvv 10vvvvvv 10vvvvvv 10vvvvvv | | 00200000 | 03FFFFFF | 111110vv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv | 這具有使英語文本看起來在UTF-8與ASCII中顯得完全一樣的邊界凈化效應。如此一來, 美國人用不著在意有什么地方不對了。只是世界上其他地方的人得從鐵箍中跳過去。具體地 說,編碼為 U+0048 U+0065 U+006C U+006C U+006F 的 “Hello〃字符串將存儲為48 65 6C 6C 6F。瞧!這與地球上存儲為ASCII、ANSI與各種OEM字符集的形式完全相同。 現在,如果你非得固執地使用重音字母或者希臘字母或者Klingon字母,那么就必須使 用幾個字節來存放單個代碼點,但是美國人永遠不會意識到有什么異樣。(UTF-8同樣具有 后面這種不錯的屬性:現有的那種想用單個0字節作為空終止字符的陳舊而愚昧的字符串處 理程序,不會截掉字符串。) 到此為止,我己經說出了Unicode的三種編碼方式。將編碼存儲為2個字節的傳統方法稱 為UCS-2 (因為它有2個字節)或者UTF-16 (因為它有16位),不過仍然需要弄清它是高端 存放模式的UCS-2,還是低端存放模式的UCS-2。還有一種流行的新UTF-8標準,它同樣可以 很自然地使用,要是你碰巧處理令人滿意的英語文本,并擁有完全不知道世間除了 ASCII之 外還有其他何物的死腦筋程序的話[4]。 其實,還有一些其他的Unicode編碼方式。一種稱之為UTF-7的編碼體系與UTF-8有許多 相似之處,只不過它保證高位總是為0。這樣一來,要是你必須通過某類極權國家的嚴密電 子郵件系統傳遞Unicode,它可以在高壓之下仍然毫發無損。這種電子郵件系統認為用7位二 進制表示一個字符完全夠用了,謝謝。另外一種稱為UCS-4的編碼方式用4個字節存放一個代 碼點,它雖然具有這樣一種良好性能:將各個代碼點都存儲為個數相同的字節,不過,天哪! 即使得克薩斯人也不會魯莽到浪費那么多的內存。 事實上,既然以Unicode代碼點形式的柏拉圖理想字母來思考問題,Unicode代碼點也就 能夠按任何守舊的編碼方案進行編碼了!比如,可以用ASCII編碼方案、老式OEM希臘編碼 方案、希伯來ANSI編碼方案或者幾百種現有編碼方案之一來編碼Unicode字符串“Hello” (U+0048 U+0065 U+006C U+006C U+006F),只是要抓住一點:一些字母可能是不出現的! 如果在試圖使用的編碼方案中沒有相應Unicode代碼點的等價內容,那么通常會顯示一個小 問號“?〃,或者更為先進一些的話,就顯示一個方框。 傳統編碼方式不下幾百種,不過它們僅僅能夠正確地存放一些代碼點,將所有其他的代 碼點轉變為問號。流行的英語文本編碼方案有Windows-1252 (西歐語種的Windows 9x標準) 與ISO-8859-1,即Latin-1 (同樣對任何西歐語種都有用)[5]。不過,如果試圖用這類編碼 方式存放俄語或者希伯來語字母,那么得到的結果就會是一簇問號。UTF 7, 8, 16與32都具 有能夠正確存放任何代碼點的優良特性。 [1]關于字節順序標記的更多信息,參見 msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_42jv.asp。 [2]見 www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt 。 [3]關于UTF-8的更多信息,參見 www.utf-8.com/ 。 [4]見 www.zvon.org/tmRFC/RFC2279/Output/chapter2.html 。 [5]關于 ISO 8859-1 字符集的概況,參見 www.htmlhelp.com/reference/charset 。 ## 編碼中惟一最重要的事實 即使你將我剛才所說的一切忘得一干二凈,我也要請你記住一個極其重要的事實。有一 個字符串,而不知道它所使用的編碼方案是毫無意義的。你再也不用將腦袋緊貼在沙地上而 假想“純”文本就是ASCII。 ## 純文本不是這么一回事 如果在內存、文件或者電子郵件中有一個字符串,那么應該知道它使用的是什么編碼方 案,否則就不能將它正確地解釋或者顯示給用戶。 其實,幾乎所有諸如“Web網頁好像是亂碼”或者“在使用重音字母時無法閱讀電子郵 件”之類的愚蠢問題,都可以歸結到天真的程序員身上,他不了解這樣一個簡單的事實,即 如果不告訴我某個特定字符串是用UTF-8,或者ASCII,或者ISO 8859-1 (Latin 1),或者 Windows 1252 (西歐)編碼的,那么我就不能正確地顯示,乃至弄清楚它在什么地方結束。 編碼方案數以百計,而在代碼點127以上,拍腦袋己經沒有效果了。 如何保存字符串用到的編碼方案信息?喏,做這件事情有許多標準的方式。對于電子郵 件信息,需要在表窗體標題放一個字符串 ``` Content-Type: text/plain; charset="UTF-8" ``` 對于Web頁,最初的想法是Web服務器通過網頁本身返回一個類似于Content-Type的http ——不是在HTML當中而是作為一個響應標題在HTML頁面之前返回。 這會引起一些問題。假設一個大型Web服務器擁有許多站點,成千上萬的網頁是由許多 人使用多種不同語言發布的,網頁使用任何在Microsoft FrontPage看來很適合產生的編碼 方案。由于Web服務器實際上不知道每個文件究竟是用什么編碼方案寫成的,因此它不能發 送 Content- Type 標題。 如果能夠使用某種特殊標記將HTML文件的Content-Type內容自然地放在HTML文件當中, 會是很方便的。當然,這會讓純化論者發瘋。不過,在知道HTML文件使用了什么編碼方案之 前,如何讀取HTML文件呢?!幸運的是,幾乎每個用得很普遍的編碼方案,對碼值32與127 之間的字符都以相同方式進行處理。因此,人們總是可以充分展示HTML頁而不必使用一些很 稀奇的字母: ``` <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> ``` 不過,這里的meta標記確實位于段中很_*_前的位置。因為只要Web瀏覽器看到該標記,它就會停止解析頁面,并在使用指定的編碼方案重新解釋整個頁面之后進入下一輪。 如果Web瀏覽器在http報頭與meta標記中都找不到任何Content-Type,該怎么辦呢? Internet Explore「確實做了件非常有趣的事情:基于不同語言以典型編碼方案得到的典型 文本中各種字節出現的頻率,試圖猜出使用了什么語言與編碼方案。由于各類不同的舊8字 節代碼頁傾向于將其母語字母放在128與255之間的不同范圍中,并且人類使用的每個語種具 有不同特征的字母使用概率分布,因此使這種做法確實有了發揮作用的機會。是的,它確實 顯得有點怪異。不過,這種做法通常能夠滿足那些從不知道要使用Content-Type報頭的幼稚 網頁編寫人員,在Web瀏覽器中閱讀網頁的需要,并且看起來還很不錯。 直到有一天,編寫的網頁確實與母語的字母使用概率分布情形不一致,Internet Explore「則認定它是朝鮮語而加以顯示。在我看來,這表明了一點,Jon Postel的格言“寬 進窄出”實在不是一條好的工程原則[1]。不管怎么說,一旦用保加利亞語寫的網站卻以朝 鮮語的形式出現(甚至是毫不相干的朝鮮文),可憐的讀者該怎么辦呢?使用菜單“View | Encoding (視圖|編碼)”試圖應用一組不同的編碼方案(東歐語種可用的編碼方案至少有 一打),直到圖片能夠較為清楚地顯示出來為止。即使這位仁兄知道如此行事,可大多數人 卻不知道這一招。 對于由本人的公司Fog Creek Software發布的Web站點管理軟件最新版本CityDesk[2] 而言,我們決定在內部用UCS-2 (2字節)Unicode處理一切。這種編碼形式也是VisualBasic、 COM與Windows NT/2000/XP作為其固有字符串類型使用的。在C++代碼中,字符串就定義為 wchar_t而不是char,并且使用以wcs打頭的函數而不是以st「打頭的函數(比如說,使用 wcscat與wcslen,而不是strcat與strlen)。要用C代碼生成UCS-2文本字符串,就得像 L”Hello”—樣在字符串前面放一個L。 CityDesk發布Web頁時,將網頁轉換為UTF-8編碼形式,該編碼方案多年來一直得到Web 瀏覽器的良好支持。“Joel on Software ”(《Joel說軟件》)的29種語言版本全部使用這 種方式編碼,我迄今為止還沒有收到一個人的來信說他在瀏覽網頁內容時遇到任何麻煩[3]。 本章寫得有點長,并且未能覆蓋需要了解的一切字符編碼與Unicode內容。但我還是希望你好好地讀一讀,它對于你回過頭來看編程有一定好處,正如用抗生素,而不是猛藥與符 咒。這就是我現在留給你去做的一項任務。 [1]Jon Postel的話,引自信息科學學院的1981年9月發布的文檔“RFC 791 - Internet Protocol” 。 [2]見 www.fogcreek.com/CityDesk 。 [3] www.Joelonsoftware.com/navLinks/OtherLanguages.html 。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看