【轉載】計算機程序的思惟邏輯 (6) - 如何從亂碼中恢復 (上)?

咱們在處理文件、瀏覽網頁、編寫程序時,時不時會碰到亂碼的狀況。亂碼幾乎老是使人心煩,讓人困惑。但願經過本節和下節文章,你能夠自信從容地面對亂碼,恢復亂碼。算法

談亂碼,咱們就要談數據的二進制表示,咱們已經在前兩節談過整數和小數的二進制表示,接下了咱們將討論字符和文本的二進制表示。瀏覽器

因爲內容比較多,咱們將分兩節來介紹。本節主要介紹各類編碼,亂碼產生的緣由,以及簡單亂碼的恢復。下節咱們介紹複雜亂碼的恢復。編輯器

編碼和亂碼聽起來比較複雜,文章也比較長,但其實並不複雜,請耐心閱讀,讓咱們逐步來探討。編碼

ASCII代理

世界上雖然有各類各樣的字符,但計算機發明之初沒有考慮那麼多,基本上只考慮了美國的需求,美國大概只須要128個字符,美國就規定了這128個字符的二進制表示方法。code

這個方法是一個標準,稱爲ASCII編碼,全稱是American Standard Code for Information Interchange,美國信息互換標準代碼。orm

128個字符用7個位恰好能夠表示,計算機存儲的最小單位是byte,即8位,ASCII碼中最高位設置爲0,用剩下的7位表示字符。這7位能夠看作數字0到127,ASCII碼規定了從0到127個,每一個數字表明什麼含義。blog

咱們先來看數字32到126的含義,以下圖所示,除了中文以外,咱們日常用的字符基本都涵蓋了,鍵盤上的字符大部分也都涵蓋了。ci

數字32到126表示的這些字符都是可打印字符,0到31和127表示一些不能夠打印的字符,這些字符通常用於控制目的,這些字符中大部分都是不經常使用的,下表列出了其中相對經常使用的字符。
it

Ascii 碼對美國是夠用了,但對別的國家而言倒是不夠的,因而,各個國家的各類計算機廠商就發明了各類各類的編碼方式以表示本身國家的字符,爲了保持與Ascii 碼的兼容性,通常都是將最高位設置爲1。也就是說,當最高位爲0時,表示Ascii碼,當爲1時就是各個國家本身的字符。

在這些擴展的編碼中,在西歐國家中流行的是ISO 8859-1和Windows-1252,在中國是GB2312,GBK,GB18030和Big5,咱們逐個來看下這些編碼。

ISO 8859-1

ISO 8859-1又稱Latin-1,它也是使用一個字節表示一個字符,其中0到127與Ascii同樣,128到255規定了不一樣的含義。

在128到255中,128到159表示一些控制字符,這些字符也不經常使用,就不介紹了。160到255表示一些西歐字符,以下圖所示:

Windows-1252

ISO 8859-1雖然號稱是標準,用於西歐國家,但它連歐元() 這個符號都沒有,由於歐元比較晚,而標準比較早。實際使用中更爲普遍的是Windows-1252編碼,這個編碼與ISO8859-1基本是同樣的,區別 只在於數字128到159,Windows-1252使用其中的一些數字表示可打印字符,這些數字表示的含義,以下圖所示:

這個編碼中加入了歐元符號以及一些其餘經常使用的字符。基本上能夠認爲,ISO 8859-1已被Windows-1252取代,在不少應用程序中,即便文件聲明它採用的是ISO 8859-1編碼,解析的時候依然被當作Windows-1252編碼。

HTML5 甚至明確規定,若是文件聲明的是ISO 8859-1編碼,它應該被看作Windows-1252編碼。爲何要這樣呢?由於大部分人搞不清楚ISO 8859-1和Windows-1252的區別,當他說ISO 8859-1的時候,其實他實際指的是Windows-1252,因此標準乾脆就這麼強制了。

GB2312

美國和西歐字符用一個字節就夠了,但中文顯然是不夠的。中文第一個標準是GB2312。

GB2312標準主要針對的是簡體中文常見字符,包括約7000個漢字,不包括一些罕用詞,不包括繁體字。

GB2312固定使用兩個字節表示漢字,在這兩個字節中,最高位都是1,若是是0,就認爲是Ascii字符。在這兩個字節中,其中高位字節範圍是0xA1-0xF7,低位字節範圍是0xA1-0xFE。

好比,"老馬"的GB2312編碼是(16進製表示):

C0 CF C2 ED

GBK

GBK創建在GB2312的基礎上,向下兼容GB2312,也就是說,GB2312編碼的字符和二進制表示,在GBK編碼裏是徹底同樣的。

GBK增長了一萬四千多個漢字,共計約21000漢字,其中包括繁體字。

GBK一樣使用固定的兩個字節表示,其中高位字節範圍是0x81-0xFE,低位字節範圍是0x40-0x7E和0x80-0xFE。

須要注意的是,低位字節是從0x40也就是64開始的,也就是說,低位字節最高位可能爲0。那怎麼知道它是漢字的一部分,仍是一個Ascii字符呢?

其實很簡單,由於漢字是用固定兩個字節表示的,在解析二進制流的時候,若是第一個字節的最高位爲1,那麼就將下一個字節讀進來一塊兒解析爲一個漢字,而不用考慮它的最高位,解析完後,跳到第三個字節繼續解析。

GB18030

GB18030向下兼容GBK,增長了五萬五千多個字符,共七萬六千多個字符。包括了不少少數民族字符,以及中日韓統一字符。

用兩個字節已經表示不了GB18030中的全部字符,GB18030使用變長編碼,有的字符是兩個字節,有的是四個字節。

在兩字節編碼中,字節表示範圍與GBK同樣。在四字節編碼中,第一個字節的值從0x81到0xFE,第二個字節的值從0x30到0x39,第三個字節的值從0x81到0xFE,第四個字節的值從0x30到0x39。

解析二進制時,如何知道是兩個字節仍是四個字節表示一個字符呢?看第二個字節的範圍,若是是0x30到0x39就是四個字節表示,由於兩個字節編碼中第二字節都比這個大。

Big5

Big5是針對繁體中文的,普遍用於臺灣香港等地。

Big5包括1萬3千多個繁體字,和GB2312相似,一個字符一樣固定使用兩個字節表示。在這兩個字節中,高位字節範圍是0x81-0xFE,低位字節範圍是0x40-0x7E和0xA1-0xFE。

編碼彙總

咱們簡單彙總一下上面的內容。

Ascii碼是基礎,一個字節表示,最高位設爲0,其餘7位表示128個字符。其餘編碼都是兼容Ascii的,最高位使用1來進行區分。

西歐主要使用Windows-1252,使用一個字節,增長了額外128個字符。

中文大陸地區的三個主要編碼GB2312,GBK,GB18030,有時間前後關係,表示的字符數愈來愈多,且後面的兼容前面的,GB2312和GBK都是用兩個字節表示,而GB18030則使用兩個或四個字節表示。

香港臺灣地區的主要編碼是Big5。

若是文本里的字符都是Ascii碼字符,那麼採用以上所說的任一編碼方式都是一同樣的。

但若是有高位爲1的字符,除了GB2312/GBK/GB18030外,其餘編碼都是不兼容的,好比,Windows-1252和中文的各類編碼是不兼容的,即便Big5和GB18030都能表示繁體字,其表示方式也是不同的,而這就會出現所謂的亂碼。

初識亂碼

一個法國人,採用Windows-1252編碼寫了個文件,發送給了一箇中國人,中國人使用GB18030來解析這個字符,看到的就是亂碼,咱們舉個例子:

法 國人發送的是 "Pékin",Windows-1252的二進制是(採用16進制):50 E9 6B 69 6E,第二個字節E9對應é,其餘都是Ascii碼,中國人收到的也是這個二進制,可是他把它看作成了GB18030編碼,GB18030中E9 6B對應的是字符"閗i",因而他看到的就是:"P閗in",這看來就是一個亂碼。

反之也是同樣的,一個GB18030編碼的文件若是被看作Windows-1252也是亂碼。

這 種狀況下,之因此看起來是亂碼,是由於看待或者說解析數據的方式錯了。糾正的方式,只要使用正確的編碼方式進行解讀就能夠了。不少文件編輯器,如 EditPlus, NotePad++, UltraEdit都有切換查看編碼方式的功能,瀏覽器也都有切換查看編碼方式的功能,如Firefox,在菜單 "查看"->"文字編碼"中。

切換查看編碼的方式,並無改變數據的二進制自己,而只是改變了解析數據的方式,從而改變了數據看起來的樣子。(稍後咱們會提到編碼轉換,它正好相反)。

不少時候,作這樣一個編碼查看方式的切換,就能夠解決亂碼的問題。但有的時候,這樣是不夠的,咱們稍後提到。

Unicode

以上咱們介紹了中文和西歐的字符與編碼,但世界上還有不少別的國家的字符,每一個國家的各類計算機廠商都對本身經常使用的字符進行編碼,在編碼的時候基本忽略了別的國家的字符和編碼,甚至忽略了同一國家的其餘計算機廠商,這樣形成的結果就是,出現了太多的編碼,且互相不兼容。

世界上全部的字符能不能統一編碼呢?能夠,這就是Unicode。

Unicode 作了一件事,就是給世界上全部字符都分配了一個惟一的數字編號,這個編號範圍從0x000000到0x10FFFF,包括110多萬。但大部分經常使用字符都 在0x0000到0xFFFF之間,即65536個數字以內。每一個字符都有一個Unicode編號,這個編號通常寫成16進制,在前面加U+。大部分中文 的編號範圍在U+4E00到U+9FA5,例如,"馬"的Unicode是U+9A6C。

Unicode就作了這麼 一件事,就是給全部字符分配了惟一數字編號。它並無規定這個編號怎麼對應到二進制表示,這是與上面介紹的其餘編碼不一樣的,其餘編碼都既規定了能表示哪些 字符,又規定了每一個字符對應的二進制是什麼,而Unicode自己只規定了每一個字符的數字編號是多少。

那編號怎麼對應到二進制表示呢?有多種方案,主要有UTF-32, UTF-16和UTF-8。

UTF-32

這個最簡單,就是字符編號的整數二進制形式,四個字節。

但有個細節,就是字節的排列順序,若是第一個字節是整數二進制中的最高位,最後一個字節是整數二進制中的最低位,那這種字節序就叫「大端」(Big Endian, BE),不然,正好相反的狀況,就叫「小端」(Little Endian, LE)。對應的編碼方式分別是UTF-32BE和UTF-32LE。

能夠看出,每一個字符都用四個字節表示,很是浪費空間,實際採用的也比較少。

UTF-16

UTF-16使用變長字節表示:

  • 對於編號在U+0000到U+FFFF的字符 (經常使用字符集),直接用兩個字節表示。須要說明的是,U+D800到U+DBFF之間的編號實際上是沒有定義的。
  • 字符值在U+10000到U+10FFFF之間的字符(也叫作增補字符集),須要用四個字節表示。前兩個字節叫高代理項,範圍是U+D800到 U+DBFF,後兩個字節叫低代理項,範圍是U+DC00到U+DFFF。數字編號和這個二進制表示之間有一個轉換算法,本文就不介紹了。

區分是兩個字節仍是四個字節表示一個符號就看前兩個字節的編號範圍,若是是U+D800到U+DBFF,就是四個字節,不然就是兩個字節。

UTF-16也有和UTF-32同樣的字節序問題,若是高位存放在前面就叫大端(BE),編碼就叫UTF-16BE,不然就叫小端,編碼就叫UTF-16LE。

UTF-16經常使用於系統內部編碼,UTF-16比UTF-32節省了不少空間,可是任何一個字符都至少須要兩個字節表示,對於美國和西歐國家而言,仍是很浪費的。

UTF-8

UTF-8就是使用變長字節表示,每一個字符使用的字節個數與其Unicode編號的大小有關,編號小的使用的字節就少,編號大的使用的字節就多,使用的字節個數從1到4個不等。

具體來講,各個Unicode編號範圍對應的二進制格式以下圖所示:

圖中的x表示能夠用的二進制位,而每一個字節開頭的1或0是固定的。

小於128的,編碼與Ascii碼同樣,最高位爲0。其餘編號的第一個字節有特殊含義,最高位有幾個連續的1表示一共用幾個字節表示,而其餘字節都以10開頭。

對於一個Unicode編號,具體怎麼編碼呢?首先將其看作整數,轉化爲二進制形式(去掉高位的0),而後將二進制位從右向左依次填入到對應的二進制格式x中,填完後,若是對應的二進制格式還有沒填的x,則設爲0。

咱們來看個例子,'馬'的Unicode編號是:0x9A6C,整數編號是39532,其對應的UTF-8二進制格式是:

1110xxxx 10xxxxxx 10xxxxxx

整數編號39532的二進制格式是 1001 101001 101100

將這個二進制位從右到左依次填入二進制格式中,結果就是其UTF-8編碼:

11101001 10101001 10101100

16進製表示爲:0xE9A9AC

和UTF-32/UTF-16不一樣,UTF-8是兼容Ascii的,對大部分中文而言,一箇中文字符須要用三個字節表示。

Uncode編碼小結

Unicode給世界上全部字符都規定了一個統一的編號,編號範圍達到110多萬,但大部分字符都在65536之內。Unicode自己沒有規定怎麼把這個編號對應到二進制形式。

UTF- 32/UTF-16/UTF-8都在作一件事,就是把Unicode編號對應到二進制形式,其對應方法不一樣而已。UTF-32使用4個字節,UTF-16 大部分是兩個字節,少部分是四個字節,它們都不兼容Ascii編碼,都有字節順序的問題。UTF-8使用1到4個字節表示,兼容Ascii編碼,英文字符 使用1個字節,中文字符大多用3個字節。

編碼轉換

有了Unicode以後,每個字符就有了多種不兼容的編碼方式,好比說"馬"這個字符,它的各類編碼方式對應的16進制是:

GB18030 C2 ED
Unicode編號 9A 6C
UTF-8 E9 A9 AC
UTF-16LE 6C 9A

這幾種格式之間能夠藉助Unicode編號進行編碼轉換。能夠簡化認爲,每種編碼都有一個映射表,存儲其特有的字符編碼和Unicode編號之間的對應關係,這個映射表是一個簡化的說法,實際上多是一個映射或轉換方法。

編碼轉換的具體過程能夠是,好比說,一個字符從A編碼轉到B編碼,先找到字符的A編碼格式,經過A的映射表找到其Unicode編號,而後經過Unicode編號再查B的映射表,找到字符的B編碼格式。

舉例來講,"馬"從GB18030轉到UTF-8,先查GB18030->Unicode編號表,獲得其編號是9A 6C,而後查Uncode編號->UTF-8表,獲得其UTF-8編碼:E9 A9 AC。

與前文提到的切換查看編碼方式正好相反,編碼轉換改變了數據的二進制格式,但並無改變字符看上去的樣子。

再看亂碼

在前文中,咱們提到亂碼出現的一個重要緣由是解析二進制的方式不對,經過切換查看編碼的方式就能夠解決亂碼。

但若是怎麼改變查看方式都不對的話,那頗有可能就不只僅是解析二進制的方式不對,而是文本在錯誤解析的基礎上還進行了編碼轉換。

咱們舉個例子來講明:

  1. 兩個字 "老馬",原本的編碼格式是GB18030,編碼是(16進制): C0 CF C2 ED。
  2. 這個二進制形式被錯誤當成了Windows-1252編碼, 解讀成了字符 "ÀÏÂí"
  3. 隨後這個字符進行了編碼轉換,轉換成了UTF-8編碼,形式仍是"ÀÏÂí",但二進制變成了:C3 80 C3 8F C3 82 C3 AD,每一個字符兩個字節。
  4. 這個時候,再按照GB18030解析,字符就變成了亂碼形式"脌脧脗鉚", 並且這時不管怎麼切換查看編碼的方式,這個二進制看起來都是亂碼。

這種狀況是亂碼產生的主要緣由。

這種狀況其實很常見,計算機程序爲了便於統一處理,常常會將全部編碼轉換爲一種方式,好比UTF-8, 在轉換的時候,須要知道原來的編碼是什麼,但可能會搞錯,而一旦搞錯,並進行了轉換,就會出現這種亂碼。

這種狀況下,不管怎麼切換查看編碼方式,都是不行的。

那有沒有辦法恢復呢?若是有,怎麼恢復呢?

相關文章
相關標籤/搜索