咱們知道,計算機只能識別諸如 0101 這樣的二進制數,因而人們必須以二進制數據與計算機進行交互,或者先將人類使用的字符按必定規則轉換爲二進制數。
那什麼是字符呢?在計算機領域,咱們把諸如文字、標點符號、圖形符號、數字等統稱爲字符。而由字符組成的集合則成爲字符集,字符集因爲包含字符的多少與異同而造成了各類不一樣的字符集。
咱們知道,全部字符在計算機中都是以二進制來存儲的。那麼一個字符究竟由多少個二進制位來表示呢?這就涉及到字符編碼的概念了,好比一個字符集有 8 個字符,那麼用 3 個二進制位就能夠徹底表示該字符集的全部字符,也即每一個字符用 3 個二進制位進行編碼。
咱們規定字符編碼必須完成以下兩件事:
(1)規定一個字符集中的字符由多少個字節表示
(2)制定該字符集的字符編碼表,即該字符集中每一個字符對應的(二進制)值。
1. ASCII 碼:
上個世紀 60 年代,美國製定了一套字符編碼標準,對英語字符與二進制位之間的關係,作了統一規定。這被稱爲 ASCII 碼,一直沿用至今。
ASCII(American Standard Code for Information Interchange),是一種字符編碼標準,它的字符集爲英文字符集,它規定字符集中的每一個字符均由一個字節表示,指定了字符表編碼表,稱爲 ASCII 碼錶。它已被國際標準化組織定義爲國際標準,稱爲 ISO646 標準。
ASCII 碼一共規定了 128 個字符的編碼,好比空格「SPACE」是 32(二進制00100000),大寫的字母 A是 65(二進制01000001)等。這 128 個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面 7 位,最前面的 1 位統一規定爲 0。這種採用一個字節來編碼 128 個字符的 ASCII 碼稱爲標準 ASCII 碼或者基礎 ASCII 碼。
可是,因爲標準 ASCII 字符集字符數目有限,在實際應用中每每沒法知足要求。爲此,國際標準化組織又制定了 ISO 2022 標準,它規定了在保持與 ISO646 兼容的前提下將 ASCII 字符集擴充爲 8 位代碼的統一方法。 ISO 陸續制定了一批適用於不一樣地區的擴充 ASCII 字符集,每種擴充 ASCII 字符集分別能夠擴充 128 個字符,這些擴充字符的編碼均爲高位爲 1 的 8 位代碼(即十進制數 128~255 ),稱爲擴展 ASCII 碼。
可是須要注意,各類擴展 ASCII 碼除了編碼爲 0~127 的字符外,編碼爲 128~255 的字符並不相同。好比,130 在法語編碼中表明瞭 é,在希伯來語編碼中卻表明了字母 Gimel (?),在俄語編碼中又會表明另外一個符號。
2. ANSI編碼標準
標準 ASCII 碼和擴展 ASCII 碼知足了西語國家的需求,可是,隨着計算機在世界範圍內的普及,對於亞洲國家,如中日韓等國來講,他們使用的符號不少,ASCII 字符編碼標準遠遠不能知足其須要,因而這些國家便針對本國的字符集指定了相應的字符編碼標準,如 GB23十二、BIG五、JIS 等僅適用於本國字符集的編碼標準。
這些字符編碼標準統稱爲 ANSI 編碼標準,這些 ANSI 編碼標準有一些共同的特色:
(1)每種 ANSI 字符集只規定本身國家或地區使用的語言所需的'字符',好比簡體中文編碼標準 GB-2312 的字符集中就不會包含韓國人的文字。
(2)ANSI 字符集的空間都比 ASCII 要大不少,一個字節已經不夠,絕大多數 ANSI 編碼標準都使用多個字節來表示一個字符。
(3)ANSI 編碼標準通常都會兼容 ASCII 碼。
這裏要特別提一下我國的幾種字符編碼標準:GB23十二、GBK、GB18030。
字符必須編碼後才能被計算機處理。計算機使用的默認編碼方式就是計算機的內碼。早期的計算機使用 7 位的 ASCII 編碼(標準 ASCII 編碼),爲了處理漢字,程序員設計了用於簡體中文的 GB2312 和用於繁體中文的 big5。
GB2312(1980年) 一共收錄了 7445 個字符,包括 6763 個漢字和 682 個其它符號。漢字區的內碼範圍高字節從 B0-F7,低字節從 A1-FE,佔用的碼位是 72*94=6768。其中有 5 個空位是 D7FA-D7FE。
GB2312 支持的漢字太少。1995年的漢字擴展規範 GBO1.0 收錄了 21886 個符號,它分爲漢字區和圖形符號區。漢字區包括 21003 個字符。2000 年的 GB18030 是取代 GBO1.0 的正式國家標準。該標準收錄了 27484 個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。如今的 PC 平臺必須支持 GB18030,對嵌入式產品暫不做要求。因此手機、MP3通常只支持 GB2312。
例如,在 Windows 中打開記事本,"另存爲"對話框的"編碼"下拉框中有一項 ANSI 編碼,ANSI 是默認的編碼方式。對於英文文件是 ASCII 編碼,對於簡體中文文件是 GB2312 編碼(只針對Windows簡體中文版,若是是繁體中文版會採用 Big5 碼),在日文操做系統下,ANSI 編碼表明 JIS 編碼,其餘語言的系統的狀況相似。
3. Unicode、UCS 和 UTF
可是隨着互聯網的興起,問題又出現了。因爲 ANSI 碼的第一個特色:各個國家或地區在編制本身的 ANSI 碼時並未考慮到其餘國家或地區的 ANSI 碼,致使編碼空間有重疊,好比:漢字'中'的編碼是[0xD6,0xD0],這個編碼在其餘國家的 ANSI 編碼標準中則不必定就是該編碼了。因而,同一個二進制數字能夠被解釋成不一樣的符號。所以,要想打開一個文本文件,就必須知道它的編碼方式,不然用錯誤的編碼方式解讀,就會出現亂碼。這樣一來當在不一樣ANSI編碼標準之間進行信息交換和顯示的時候,亂碼就不可避免了。
(1)Unicode
能夠想象,若是有一種編碼,將世界上全部的符號都歸入其中,每個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是 Unicode,就像它的名字所表示的,這是一種全部符號的編碼。
Unicode 是 Universal Multiple-Octet Coded Character Set 的縮寫,中文含義是"通用多八位編碼字符集"。它是由一個名爲 Unicode 學術學會(Unicode。org)的機構制訂的字符編碼標準,Unicode 目標是將世界上絕大多數國家的文字、符號都編入其字符集,它爲每種語言中的每一個字符設定了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求,以達到支持現今世界各類不一樣語言的書面文本的交換、處理及顯示的目的,使世界範圍人們經過計算機進行信息交換時達到暢通自如而無障礙。
因爲一個 Unicode 字符用多個字節表示。這樣 Unicode 編碼在不一樣平臺存儲時就要注意其字節序了。好比:採用標準Unicode編碼的'中'在 X86 平臺上(big endian)的存儲就是 '2D4E',而在 SPARC Solaris 上(little endian)的存儲則是 '4E2D'。
(2) UCS
那什麼又是 UCS 呢,它與 Unicode 有何關係?
歷史上,有兩個獨立創立統一字符集的嘗試。一個是國際標準化組織(ISO)的 ISO10646 項目, 另外一個是由(一開始大可能是美國的)多語言軟件製造商組成的協會(unicode.org)組織的 Unicode 項目。 幸運的是, 1991 年先後, 兩個項目的參與者都認識到, 世界不須要兩個不一樣的統一字符集。 它們合併雙方的工做成果, 併爲創立一個統一編碼表而協同工做。如今,兩個項目仍都存在並獨立地公佈各自的標準, 但 Unicode 協會和 ISO/IEC JTC1/SC2 都贊成保持 Unicode 和 ISO10646 標準的碼錶兼容, 並緊密地共同調整任何將來的擴展。
國際標準 ISO10646 定義了通用字符集 (Universal Character Set) UCS。 UCS 是全部其餘字符集標準的一個超集。 它保證與其餘字符集是雙向兼容的。 就是說, 若是你將任何文本字符串翻譯到 UCS 格式, 而後再翻譯回原編碼, 你不會丟失任何信息。
ISO10646 定義了一個 31 位的字符集(4 個字節)。然而,在這巨大的編碼空間中,迄今爲止只分配了前 65534 個碼位 (0x0000 到 0xFFFD)。 這個 UCS 的 16 位子集稱爲基本多語言面(Basic Multilingual Plane,BMP)。將被編碼在 16 位 BMP 之外的字符都屬於很是特殊的字符(好比象形文字),且只有專家在歷史和科學領域裏纔會用到它們。按當前的計劃, 未來也許不再會有字符被分配到從 0x000000 到 0x10FFFF 這個覆蓋了超過 100 萬個潛在的將來字符的 21 位的編碼空間之外去了。ISO10646-1 標準第一次發表於 1993 年, 定義了字符集與 BMP 中內容的架構。定義 BMP 之外的字符編碼的第二部分 ISO 10646-2 正在準備中, 但也許要過好幾年才能完成。 新的字符仍源源不斷地加入到 BMP 中, 但已經存在的字符是穩定的且不會再改變了。
UCS 不只給每一個字符分配一個代碼, 並且賦予了一個正式的名字。 表示一個 UCS 值的十六進制數, 一般在前面加上 "U+", 就象 U+0041 表明字符"拉丁大寫字母A"。UCS 字符 U+0000 到 U+007F 與 US-ASCII(ISO646) 是一致的, U+0000 到 U+00FF 與 ISO 8859-1(Latin-1) 也是一致的。從 U+E000 到 U+F8FF, 已經 BMP 之外的大範圍的編碼是爲私用保留的。
Unicode 字符編碼標準與 ISO10646 的通用字符集(Universal Character Set,UCS)概念相對應,目前的用於實用的 Unicode 版本對應於 UCS-2,即便用 16 位來表示一個 Unicode 字符。也就是每一個字符佔用 2 個字節。這樣理論上一共最多能夠表示 65536(2 的 16 次方) 個字符。基本知足各類語言的使用。
實際上目前版本的 Unicode 還沒有填充滿這 16 位編碼,保留了大量空間做爲特殊使用或未來擴展。將來版本會擴充到 ISO 10646-1 實現級別 3,即涵蓋 UCS-4 的全部字符。UCS-4 是一個更大的還沒有填充徹底的31位字符集,加上恆爲 0 的首位,共需佔據 32 位,即 4 字節。理論上最多能表示 2147483648(2的31次方)個字符,徹底能夠涵蓋一切語言所用的符號。
因爲Unicode 編碼標準與 UCS 編碼標準是相互兼容的,爲了方便敘述,下面把兩者做爲一個統一編碼標準來敘述。
(3)UTF
Unicode(UCS)只是一個字符集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。
好比,漢字「嚴」的 Unicode(UCS)碼是十六進制數 4E25,轉換成二進制數足足有 15 位(100111000100101),也就是說這個符號的表示至少須要 2 個字節。表示其餘更大的符號,可能須要 3 個字節或者 4 個字節,甚至更多。
這裏就有兩個嚴重的問題,第一個問題是,如何才能區別 Unicode 和 ASCII?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號呢?第二個問題是,咱們已經知道,英文字母只用一個字節表示就夠了,若是unicode統一規定,每一個符號用三個或四個字節表示,那麼每一個英文字母前都必然有二到三個字節是0,這對於存儲來講是極大的浪費,文本文件的大小會所以大出二三倍,這是沒法接受的。
爲了解決這些問題,就出現了 UTF。
UTF(Unicode Translation Format),它是 Unicode (UCS)的實現(或存儲)方式,稱爲 Unicode 轉換格式。Unicode 的實現方式不一樣於編碼方式。一個字符的 Unicode 編碼是肯定的。可是在實際傳輸過程當中,因爲不一樣系統平臺的設計不必定一致,以及出於節省空間的目的,對 Unicode 編碼的實現方式有所不一樣。
UTF 有三種實現方式:
UTF-16:其自己就是標準的 Unicode 編碼方案,又稱爲 UCS-2,它固定使用 16 bits(兩個字節)來表示一個字符。
UTF-32:又稱爲UCS-4,它固定使用32 bits(四個字節)來表示一個字符。
UTF-8:最普遍的使用的 UTF 方案,UTF-8 使用可變長度字節來儲存 Unicode 字符,例如 ASCII 字母繼續使用 1 字節儲存,重音文字、希臘字母或西裏爾字母等使用 2 字節來儲存,而經常使用的漢字就要使用 3 字節。輔助平面字符則使用 4 字節。UTF-8 更便於在使用 Unicode 的系統與現存的單字節的系統進行數據傳輸和交換。與前兩個方案不一樣:UTF-8 以字節爲編碼單元,沒有字節序的問題。
UTF 有三種方案,那麼如何在接收數據和存儲數據時識別數據採用的是哪一個方案呢?
Unicode(UCS)規範中推薦的標記字節順序的方法是 BOM。BOM 是 Byte order Mark。
在 UCS 編碼中有一個叫作「ZERO WIDTH NO-BREAK SPACE」(零寬度非換行空格)的字符,它的編碼是 FEFF。而 FFFE 在 UCS 中是不存在的字符,因此不會出如今實際傳輸中。UCS 規範建議咱們在傳輸字節流前,先傳輸字符「ZERO WIDTH NO-BREAK SPACE」。這樣若是接收者收到 FEFF,就代表這個字節流是 Big-Endian 的;若是收到 FFFE,就代表這個字節流是 Little-Endian 的。所以字符「ZERO WIDTH NO-BREAK SPACE」又被稱做 BOM。
UTF-8 不須要 BOM 來代表字節順序,但能夠用 BOM 來代表編碼方式。字符「ZERO WIDTH NO-BREAK SPACE」的 UTF-8 編碼是 EF BB BF。因此若是接收者收到以 EF BB BF 開頭的字節流,就知道這是 UTF-8 編碼了。Windows 就是使用 BOM 來標記文本文件的編碼方式的。
這樣根據識別前面的「ZERO WIDTH NO-BREAK SPACE」字符便可識別編碼方案,字節流中前幾個字節所表示的編碼方式以下:程序員
EF BB BF網絡 |
UTF-8 架構 |
FE FF編碼 |
UTF-16/UCS-2, little endianspa |
FF FE操作系統 |
UTF-16/UCS-2, big endian 翻譯 |
FE FF 00 00設計 |
UTF-32/UCS-4, little endiancode |
00 00 FF FEorm |
UTF-32/UCS-4, big-endian |
在微軟公司Windows XP操做系統附帶的記事本中,「另存爲」對話框能夠選擇的四種編碼方式除去非 Unicode 編碼的 ANSI 外,其他三種「Unicode」、「Unicode big endian」和「UTF-8」分別對應 UTF-16 小端(BOM)、UTF-16 大端(BOM)和 UTF-8 這三種實現方式。
另外,內碼是指操做系統內部的字符編碼。早期操做系統的內碼是與語言相關的。目前 Windows 的內核已經支持 Unicode 字符集,這樣在內核上能夠支持全世界全部的語言文字。可是因爲現有的大量程序和文檔都採用了某種特定語言的編碼,例如 GBK,Windows 不可能不支持現有的編碼,而所有改用 Unicode。因而 Windows 就使用代碼頁(code page)來適應各個國家和地區不一樣的字符集。
而所謂代碼頁(code page)就是針對一種語言文字的字符編碼。例如 GBK 的 code page 是 CP936,BIG5 的 code page 是 CP950,GB2312 的 code page 是 CP20936。
微軟通常將默認代碼頁指定的編碼說成是內碼。默認代碼頁指的是:默認用什麼編碼來解釋字符。例如 Windows 的記事本打開了一個文本文件,裏面的內容是字節流:BA、BA、D七、D6。Windows 應該去怎麼解釋它呢?
是按照 Unicode 編碼解釋、仍是按照 GBK 解釋、仍是按照 BIG5 解釋,仍是按照 ISO8859-1 去解釋?若是按 GBK 去解釋,就會獲得「漢字」兩個字。按照其它編碼解釋,可能找不到對應的字符,也可能找到錯誤的字符。所謂「錯誤」是指與文本做者的本意不符,這時就產生了亂碼。
答案是 Windows 按照當前的默認代碼頁去解釋文本文件裏的字節流。默認代碼頁能夠經過控制面板的區域選項設置。記事本的另存爲中有一項 ANSI,其實該項就是指按照默認代碼頁的編碼方法保存。
注1:Unicode 編碼轉換爲 UTF-8 編碼的方法
UTF-8 就是以 8 位爲單元對 Unicode 進行編碼。下面是 Unicode 和 UTF-8 轉換的規則:
Unicode |
UTF-8 |
0000 - 007F |
0xxxxxxx |
0080 - 07FF |
110xxxxx 10xxxxxx |
0800 - FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
例如「漢」字的Unicode編碼是 6C49。6C49 在 0800-FFFF 之間,因此確定要用 3 字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將 6C49 寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的 x,獲得:11100110 10110001 10001001,即 E6 B1 89。
注2:當一個軟件打開一個文本時,它要作的第一件事是決定這個文本到底是使用哪一種字符集的哪一種編碼保存的。軟件通常採用三種方式來決定文本的字符集和編碼:檢測文件頭標識,提示用戶選擇,根據必定的規則猜想。
最標準的途徑是檢測文本最開頭的幾個字節。
例如,當你在 Windows 的記事本里新建一個文件,輸入「聯通」兩個字以後,保存,關閉,而後再次打開,你會發現這兩個字已經消失了,代之的是幾個亂碼。當你新建一個文本文件時,記事本的編碼默認是 ANSI(表明系統默認編碼,在中文系統中是 GB 系列編碼)。在這種編碼下,"聯通"的內碼是:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意,第一二個字節、第三四個字節的起始部分的都是"110"和"10",正好與 UTF-8 規則裏的兩字節模板是一致的。因而當咱們再次打開記事本時,記事本就誤認爲這是一個 UTF-8 編碼的文件,讓咱們把第一個字節的 110 和第二個字節的 10 去掉,咱們就獲得了「00001 101010」,再把各位對齊,補上前導的 0,就獲得了「0000 0000 0110 1010」,很差意思,這是 UNICODE 的 006A,也就是小寫的字母"j",而以後的兩字節用 UTF-8 解碼以後是 0368,這個字符什麼也不是。這就是隻有「聯通」兩個字的文件沒有辦法在記事本里正常顯示的緣由。
其實,若是記事本軟件經過檢測文件頭標識來肯定文件的編碼方式就可避免該狀況,即若是是 UTF-8 文件,則其文件前三個字節應該是 EF BB BF。
注3:地球人都知道,大多數Intel兼容機都採用小端法表示數據,而大多數IBM和Sun Microsystems的機器則採用大端法表示數據。例如 0x1234567 這個數,大端法在內存中按字節依次存放爲:01 23 45 67,小端法在內存中按字節依次存放爲:67 45 23 01。
不多有魚油知道他們事實上是來源於Jonathan Swift的《格列佛遊記》一書。
如下是Jonathan Swift 在1726 年關於大小端之爭歷史的描述:
我下面要告訴你的是,Lilliput 和Blefuscu 這兩大強國在過去36 個月裏一直在苦戰。 戰爭開始是因爲如下的緣由:咱們你們都認爲,吃雞蛋前,原始的方法是打破雞蛋較大的一端, 但是當今皇帝的祖父小時候吃雞蛋,一次按古法打雞蛋時碰巧將一個手指弄破了,所以他的父 親,當時的皇帝,就下了一道敕令,命令全體臣民吃雞蛋時打破雞蛋較小的一端,違令者重罰。 老百姓們對這項命令極爲反感。歷史告訴咱們,由此曾發生過6 次叛亂,其中一個皇帝送了命, 另外一個丟了王位。這些叛亂大多都是由Blefuscu 的國王大臣們煽動起來的。叛亂平息後,流亡 的人老是逃到那個帝國去尋救避難。據估計,前後幾回有11 000 人情願受死也不願去打破雞蛋 較小的一端。關於這一爭端,曾出版過幾百本大部著做,不過大端派的書一直是受禁的,法律也 規定該派的任何人不得作官。(此段譯文摘自網上蔣劍鋒譯的《格列佛遊記》第一卷第4 章。)
在他那個時代,Swift 是在諷刺英國(Lilliput)和法國(Blefuscu)之間持續的衝突。Danny Cohen,一位網絡協議的早期開創者,第一次使用這兩個術語來指代字節順序[25],後來這個術 語被普遍接納了。