2. Unicode和UTF-8

為了統一全世界各國語言文字和專業領域符號(例如數學符號、樂譜符號)的編碼,ISO制定了ISO 10646標準,也稱為UCS(Universal Character Set)。UCS編碼的長度是31位,可以表示231個字元。如果兩個字元編碼的高位相同,只有低16位不同,則它們屬於一個平面(Plane),所以一個平面由216個字元組成。目前常用的大部分字元都位於第一個平面(編碼範圍是U-00000000~U-0000FFFD),稱為BMP(Basic Multilingual Plane)或Plane 0,為了向後兼容,其中編號為0~256的字元和Latin-1相同。UCS編碼通常用U-xxxxxxxx這種形式表示,而BMP的編碼通常用U+xxxx這種形式表示,其中x是十六進制數字。在ISO制定UCS的同時,另一個由廠商聯合組織也在着手制定這樣的編碼,稱為Unicode,後來兩家聯手制定統一的編碼,但各自發佈各自的標準文檔,所以UCS編碼和Unicode碼是相同的。

有了字元編碼,另一個問題就是這樣的編碼在計算機中怎麼表示。現在已經不可能用一個位元組表示一個字元了,最直接的想法就是用四個位元組表示一個字元,這種表示方法稱為UCS-4或UTF-32,UTF是Unicode Transformation Format的縮寫。一方面這樣比較浪費存儲空間,由於常用字元都集中在BMP,高位的兩個位元組通常是0,如果只用ASCII碼或Latin-1,高位的三個位元組都是0。另一種比較節省存儲空間的辦法是用兩個位元組表示一個字元,稱為UCS-2或UTF-16,這樣只能表示BMP中的字元,但BMP中有一些擴展字元,可以用兩個這樣的擴展字元表示其它平面的字元,稱為Surrogate Pair。無論是UTF-32還是UTF-16都有一個更嚴重的問題是和C語言不兼容,在C語言中0位元組表示字元串結尾,庫函數strlenstrcpy等等都依賴于這一點,如果字元串用UTF-32存儲,其中有很多0位元組並不表示字元串結尾,這就亂套了。

UNIX之父Ken Thompson提出的UTF-8編碼很好地解決了這些問題,現在得到廣泛應用。UTF-8具有以下性質:

具體來說,UTF-8編碼有以下幾種格式:

U-00000000 – U-0000007F:  0xxxxxxx
U-00000080 – U-000007FF:  110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF:  1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF:  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF:  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF:  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

第一個位元組要麼最高位是0(ASCII位元組),要麼最高兩位都是1,最高位之後1的個數決定後面有多少個位元組也屬於當前字元編碼,例如111110xx,最高位之後還有四個1,表示後面有四個位元組也屬於當前字元的編碼。後面每個位元組的最高兩位都是10,可以和第一個位元組區分開。這樣的設計有利於誤碼同步,例如在網絡傳輸過程中丟失了幾個位元組,很容易判斷當前字元是不完整的,也很容易找到下一個字元從哪裡開始,結果頂多丟掉一兩個字元,而不會導致後面的編碼解釋全部混亂了。上面的格式中標為x的位就是UCS編碼,最後一種6位元組的格式中x位有31個,可以表示31位的UCS編碼,UTF-8就像一列火車,第一個位元組是車頭,後面每個位元組是車廂,其中承載的貨物是UCS編碼。UTF-8規定承載的UCS編碼以大端表示,也就是說第一個位元組中的x是UCS編碼的高位,後面位元組中的x是UCS編碼的低位。

例如U+00A9(©字元)的二進制是10101001,編碼成UTF-8是11000010 10101001(0xC2 0xA9),但不能編碼成11100000 10000010 10101001,UTF-8規定每個字元只能用儘可能少的位元組來編碼。