Unicode、UTF-8、ASCII等編碼方式淺述

關於編碼問題,雖然在開發中經常用到,但是對於各種編碼方式的實現、相互之間的轉換、在實際場景中的使用區別等方面並沒有深刻的理解。今天針對Unicode、UTF-8、ASCII、ANSI、GB2312/GBK、ISO8859-1等編碼方式做一些解釋和說明。

一、ASCII

首先從最基礎的ASCII碼開始說起。計算機真正的內部實現上,任何字符都是0和1的組合,那麼ASCII碼是最早是上世紀60年代的時候由美國人發明的用來統一表示英文大小寫字母以及常用字符的。其爲一個字節的表示,最早只用了0-127,也就是一個字節的後7個bit。這完全滿足了英文區域人民的使用,用起來有了統一標準,很嗨啊。這就是最早的統一ASCII標準。

二、擴展 ASCII 編碼

但是,隨着計算機的傳播和普及,歐洲區域的非英語系的人民需要表示自己的語言,而128個字符是不夠的,這時候就使用了最後一個高位bit,也就是0-255這256個字符的表示都用了。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。

但是,這裏又出現了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0–127表示的符號是一樣的,不一樣的只是128–255的這一段。

至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,肯定是不夠的,就必須使用多個字節表達一個符號。比如,簡體中文常見的編碼方式是 GB2312,使用兩個字節表示一個漢字,所以理論上最多可以表示 256 x 256 = 65536 個符號。

具體各種語言之間編碼關係的對應就不多談了,但是中間都是有各自使用的編碼方式和通用編碼方式之間的轉換。要不然各國之間的語言在互聯網上通信是很麻煩的。所以知道能轉換就好了,不多介紹。

但是這裏提到了通用碼錶,也就是中間轉換的通用碼錶,隨着互聯網發發展,沒有通用方式怎麼能行呢。各種各樣的編碼方式成了系統開發者的噩夢,因爲他們想把軟件賣到國外。於是,他們提出了一個“內碼錶”的概念,可以切換到相應語言的一個內碼錶,這樣才能顯示相應語言的字母。在這種情況下,如果使用多語種,那麼就需要頻繁的在內碼錶內進行切換。

三、Unicode

最終,美國人意識到他們應該提出一種標準方案來展示世界上所有語言中的所有字符,出於這個目的,Unicode誕生了。

Unicode 當然是一本很厚的字典,記錄着世界上所有字符對應的一個數字。具體是怎樣的對應關係,又或者說是如何進行劃分的,就不是我們考慮的問題了,我們只用知道 Unicode 給所有的字符指定了一個數字用來表示該字符。

對於 Unicode 有一些誤解,它僅僅只是一個字符集,規定了符合對應的二進制代碼,至於這個二進制代碼如何存儲則沒有任何規定。它的想法很簡單,就是爲每個字符規定一個 用來表示該字符的數字,僅此而已。至於怎麼存儲,怎麼表示,是各種編碼方式關心的。而Unicode的作用是通用內碼錶。

簡單介紹一下Unicode的實現:

1. 代碼點:

Unicode標準的本意很簡單:希望給世界上每一種文字系統的每一個字符,都分配一個唯一的整數,這些整數叫做代碼點Code Points)。

2.代碼空間

所有的代碼點構成一個代碼空間Code Space),根據Unicode定義,總共有1,114,112個代碼點,編號從0x00x10FFFF。換句話說,如果每個代碼點都能夠代表一個有效字符的話,Unicode標準最多能夠編碼1,114,112,也就是大概110多萬個字符。最新的Unicode標準(7.0)已經給超過11萬個字符分配了代碼點。

3.代碼平面

Unicode標準把代碼點分成了17代碼平面Code Plane),編號爲#0#16。每個代碼平面包含65,536(2^16)個代碼點(17*65,536=1,114,112)。其中,Plane#0叫做基本多語言平面(Basic Multilingual Plane,BMP),其餘平面叫做補充平面(Supplementary Planes)。Unicode7.0只使用了17個平面中的6個,並且給這6個平面起了名字,具體的:

  • Plane#0 BMP(Basic Multilingual Plane)大部分常用的字符都坐落在這個平面內,比如ASCII字符,漢字等。
  • Plane#1 SMP(Supplementary Multilingual Plane)這個平面定義了一些古老的文字,不常用。
  • Plane#2 SIP(Supplementary Ideographic Plane)這個平面主要是一些BMP中沒有包含漢字。
  • Plane#14 SSP(Supplementary Special-purpose Plane)這個平面定義了一些非圖形字符。
  • Plane#15 SPUA-A(Supplementary Private Use Area A)
  • Plane#16 SPUA-B(Supplementary Private Use Area B)

BMP是最重要的一個代碼平面,大部分常用的字符都定義在這個平面內,在BMP中定義的代碼點包括:

  • ASCII 總共有128個字符,佔據了BMP的前128個代碼點
  • ISO-8859-1 共256個字符,佔據了BMP的前256個代碼點
  • CJK Unified Ideographs 上圖的紅色區域(佔據BMP大約1/3)定義了兩萬多個漢字,其中前20,902個漢字是按照《康熙字典》裏筆畫順序排列的
  • Surrogate Code Points 從0xD800到0xDBFF的1024個代碼點是High-surrogate代碼點,從0xDC00到0xDFFF的1024個代碼點是Low-surrogate代碼點。這2048個代碼點並不是有效的字符代碼點,它們是爲UTF編碼保留的。一個High-surrogate代碼點和一個Low-surrogate代碼點組成一個代理對(Surrogate Pair),可以在UTF-16裏編碼BMP之外的某個代碼點(1024^2+65,536=1,114,112)。

通過上面的介紹,我們可以清晰的知道,Unicode只是一種碼錶,表示了所有的字符。但是,對於這些字符具體在計算機中如何存儲,如何識別並沒有解決。一個多字節表示的複雜字符,怎麼能不被誤解碼成多個單字節的簡單字符,不通語言之間如何映射等這些問題亟需解決。

四、UTF-8

這個時候UTF-8這種編碼方式應運而生。當然,任何一種解決大事情的方案都不是一蹴而就的,產生的過程也是漫長的,具體過程就不表了,簡單描述一下。

UTF-8其實不是最先產生的統一編碼方式,UTF-16、UTF-32都在它之前產生。但是UTF-16固定使用兩字節,UTF-32固定使用4字節,對於使用bit較少的字符來講,是極大的浪費。而且大多數的字符都沒有那麼多字節的表示。這時候UTF-8就產生了。UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。

UTF-8 的編碼規則很簡單,只有二條:

1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

2)對於n字節的符號(n > 1),第一個字節的前n位都設爲1,第n + 1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的 Unicode 碼。 
下表總結了編碼規則,字母x表示可用編碼的位。

Unicode 十六進制碼點範圍 UTF-8 二進制
0000 0000 - 0000 007F 0xxxxxxx
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟據上表,解讀 UTF-8 編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。 
下面,還是以漢字嚴爲例,演示如何實現 UTF-8 編碼。

嚴的 Unicode 是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800 - 0000 FFFF),因此嚴的 UTF-8 編碼需要三個字節,即格式是1110xxxx 10xxxxxx 10xxxxxx。然後,從嚴的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,嚴的 UTF-8 編碼是11100100 10111000 10100101,轉換成十六進制就是E4B8A5。

五、各種編碼方式之間的轉換

1.ANSI:這是默認編碼方式,針對英文是ASCII編碼,針對中文是GB2312的簡體中文編碼。GBK是繁體中文編碼,向下兼容GB2312。

2.UTF-16 LE&BE:UTF-16是用兩個字節來表示字符,那麼對應於Unicode中兩個字節來講,哪個在前,哪個在後呢。比如前面的嚴字來講,Unicode兩個字符是4E25,而用UTF-16來表示的話,如果是BE那麼就是4E25,而如果是LE,那麼就是254E。LE和BE分別表示Little endian 和 Big endian,就是小頭在前還是大頭在前的意思。

 這是源於英國作家斯威夫特的《格列佛遊記》。在該書中,小人國裏爆發了內戰,戰爭起因是人們爭論,吃雞蛋時究竟是從大頭(Big-endian)敲開還是從小頭(Little-endian)敲開。爲了這件事情,前後爆發了六次戰爭,一個皇帝送了命,另一個皇帝丟了王位。

第一個字節在前,就是”大頭方式”(Big endian),第二個字節在前就是”小頭方式”(Little endian)。

爲了弄清楚UTF-16文件的大小頭順序,在UTF-16文件的開首,都會放置一個U+FEFF字符作爲Byte Order Mark(UTF-16LE以FF FE代表,UTF-16BE以FE FF代表),以顯示這個文字檔案是以UTF-16編碼,其中U+FEFF字符在UNICODE中代表的意義是ZERO WIDTH NO-BREAK SPACE,顧名思義,它是個沒有寬度也沒有斷字的空白。

3.ISO8859-1 通常叫做Latin-1

ISO8859-1 通常叫做Latin-1,屬於單字節編碼,最多能表示的字符範圍是0-255,應用於英文系列。比如,字母a的編碼爲0x61=97。 很明顯,iso8859-1編碼表示的字符範圍很窄,無法表示中文字符。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用iso8859-1編碼來表示。而且在很多協議上,默認使用該編碼。比如,雖然"中文"兩個字不存在iso8859-1編碼,以gb2312編碼爲例,應該是"d6d0 cec4"兩個字符(java字符佔2個字節),使用iso8859-1編碼的時候則將它拆開爲4個字節來表示:"d6 d0 ce c4"(事實上,在進行存儲的時候,也是以字節爲單位處理的)。而如果是UTF編碼,則是6個字節"e4 b8 ad e6 96 87"。很明顯,這種表示方法還需要以另一種編碼爲基礎。

後記

至此,基本的編碼方式的理解梳理清楚了,看清細節的感覺有時候還挺好的。雖說朦朧感是美的來源,有些時候還是需要看清一些才踏實。

注:有任何問題可以一起探討,本文參考了多處博客以及編碼的官方文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章