嚴格來説,字符集和字符編碼不是一個概念:
字符集定義了字符和二進制的對應關係,為每個字符分配了唯一的編號。可以將字符集理解成一個很大的表格,它列出了所有字符和二進制的對應關係,計算機顯示文字或者存儲文字,就是一個查表的過程。
字符編碼規定了如何將字符的編號存儲到計算機中。如果使用了類似 GB2312 和 GBK 的變長存儲方案(不同的字符佔用的字節數不一樣),那麼為了區分一個字符到底使用了幾個字節,就不能將字符的編號直接存儲到計算機中,字符編號在存儲之前必須要經過轉換,在讀取時還要再逆向轉換一次,這套轉換方案就叫做字符編碼。
有的字符集在制定時就考慮到了編碼的問題,是和編碼結合在一起的,例如ASCII、GB2312、GBK、BIG5 等,所以無論稱作字符集還是字符編碼都無所謂,也不好區分兩者的概念。而有的字符集只管制定字符的編號,至於怎麼存儲,那是字符編碼的事情,Unicode 就是一個典型的例子,它只是定義了全球文字的唯一編號,我們還需要 UTF-8、UTF-16、UTF-32 這幾種編碼方案將 Unicode 存儲到計算機中。
Unicode 可以使用的編碼方案有三種,分別是:
- UTF-8:一種變長的編碼方案,使用 1~6 個字節來存儲;
- UTF-32:一種固定長度的編碼方案,不管字符編號大小,始終使用 4 個字節來存儲;
- UTF-16:介於 UTF-8 和 UTF-32 之間,使用 2 個或者 4 個字節來存儲,長度既固定又可變。
UTF 是 Unicode Transformation Format 的縮寫,意思是“Unicode轉換格式”,後面的數字表明至少使用多少個比特位(Bit)來存儲字符。
1) UTF-8
UTF-8 的編碼規則很簡單:
- 如果只有一個字節,那麼最高的比特位為 0,這樣可以兼容 ASCII;
- 如果有多個字節,那麼第一個字節從最高位開始,連續有幾個比特位的值為 1,就使用幾個字節編碼,剩下的字節均以 10 開頭。
具體的表現形式為:
0xxxxxxx:單字節編碼形式,這和 ASCII 編碼完全一樣,因此 UTF-8 是兼容 ASCII 的;110xxxxx 10xxxxxx:雙字節編碼形式(第一個字節有兩個連續的 1);1110xxxx 10xxxxxx 10xxxxxx:三字節編碼形式(第一個字節有三個連續的 1);11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字節編碼形式(第一個字節有四個連續的 1)。
xxx 就用來存儲 Unicode 中的字符編號。
下面是一些字符的 UTF-8 編碼實例(着色部分表示本來的 Unicode 編號):
| 字符 | 字母N | 符號æ | 中文⻬ |
|---|---|---|---|
| Unicode 編號(二進制) | 01001110 | 11100110 | 00101110 11101100 |
| Unicode 編號(十六進制) | 4E | E6 | 2E EC |
| UTF-8 編碼(二進制) | 01001110 |
11000011 10100110 |
11100010 10111011 10101100 |
| UTF-8 編碼(十六進制) | 4E | C3 A6 | E2 BB AC |
對於常用的字符,它的 Unicode 編號範圍是 0 ~ FFFF,用 1~3 個字節足以存儲,只有及其罕見,或者只有少數地區使用的字符才需要 4~6個字節存儲。
2) UTF-32
UTF-32 是固定長度的編碼,始終佔用 4 個字節,足以容納所有的 Unicode 字符,所以直接存儲 Unicode 編號即可,不需要任何編碼轉換。浪費了空間,提高了效率。
3) UTF-16
UFT-16 比較奇葩,它使用 2 個或者 4 個字節來存儲。
對於 Unicode 編號範圍在 0 ~ FFFF 之間的字符,UTF-16 使用兩個字節存儲,並且直接存儲 Unicode 編號,不用進行編碼轉換,這跟 UTF-32 非常類似。
對於 Unicode 編號範圍在 10000~10FFFF 之間的字符,UTF-16 使用四個字節存儲,具體來説就是:將字符編號的所有比特位分成兩部分,較高的一些比特位用一個值介於 D800~DBFF 之間的雙字節存儲,較低的一些比特位(剩下的比特位)用一個值介於 DC00~DFFF 之間的雙字節存儲。
如果你不理解什麼意思,請看下面的表格:
| Unicode 編號範圍(十六進制) | 具體的 Unicode 編號(二進制) | UTF-16 編碼 | 編碼後的字節數 |
|---|---|---|---|
| 0000 0000 ~ 0000 FFFF | xxxxxxxx xxxxxxxx | xxxxxxxx xxxxxxxx | 2 |
| 0001 0000---0010 FFFF | yyyy yyyy yyxx xxxx xxxx | 110110yy yyyyyyyy 110111xx xxxxxxxx | 4 |
位於 D800~0xDFFF 之間的 Unicode 編碼是特別為四字節的 UTF-16 編碼預留的,所以不應該在這個範圍內指定任何字符。如果你真的去查看 Unicode 字符集,會發現這個區間內確實沒有收錄任何字符。
UTF-16 要求在制定 Unicode 字符集時必須考慮到編碼問題,所以真正的 Unicode 字符集也不是隨意編排字符的。
對比以上三種編碼方案
首先,只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因為它們沒有單字節編碼。
1) UTF-8 使用盡量少的字節來存儲一個字符,不但能夠節省存儲空間,而且在網絡傳輸時也能節省流量,所以很多純文本類型的文件(例如各種編程語言的源文件、各種日誌文件和配置文件等)以及絕大多數的網頁(例如百度、新浪、163等)都採用 UTF-8 編碼。
UTF-8 的缺點是效率低,不但在存儲和讀取時都要經過轉換,而且在處理字符串時也非常麻煩。例如,要在一個 UTF-8 編碼的字符串中找到第 10 個字符,就得從頭開始一個一個地檢索字符,這是一個很耗時的過程,因為 UTF-8 編碼的字符串中每個字符佔用的字節數不一樣,如果不從頭遍歷每個字符,就不知道第 10 個字符位於第幾個字節處,就無法定位。
不過,隨着算法的逐年精進,UTF-8 字符串的定位效率也越來越高了,往往不再是槽點了。
2) UTF-32 是“以空間換效率”,正好彌補了 UTF-8 的缺點,UTF-32 的優勢就是效率高:UTF-32 在存儲和讀取字符時不需要任何轉換,在處理字符串時也能最快速地定位字符。例如,在一個 UTF-32 編碼的字符串中查找第 10 個字符,很容易計算出它位於第 37 個字節處,直接獲取就行,不用再逐個遍歷字符了,沒有比這更快的定位字符的方法了。
但是,UTF-32 的缺點也很明顯,就是太佔用存儲空間了,在網絡傳輸時也會消耗很多流量。我們平常使用的字符編碼值一般都比較小,用一兩個字節存儲足以,用四個字節簡直是暴殄天物,甚至説是不能容忍的,所以 UTF-32 在應用上不如 UTF-8 和 UTF-16 廣泛。
3) UTF-16 可以看做是 UTF-8 和 UTF-32 的折中方案,它平衡了存儲空間和處理效率的矛盾。對於常用的字符,用兩個字節存儲足以,這個時候 UTF-16 是不需要轉換的,直接存儲字符的編碼值即可。
Windows 內核、.NET Framework、Cocoa、Java String 內部採用的都是 UTF-16 編碼。UTF-16 是幕後的功臣,我們在編輯源代碼和文檔時都是站在前台,所以一般感受不到,其實很多文本在後台處理時都已經轉換成了 UTF-16 編碼。
不過,UNIX 家族的操作系統(Linux、Mac OS、iOS 等)內核都採用 UTF-8 編碼,我們就不去爭論誰好誰壞了。
寬字符和窄字符(多字節字符)
有的編碼方式採用 1~n 個字節存儲,是變長的,例如 UTF-8、GB2312、GBK 等;如果一個字符使用了這種編碼方式,我們就將它稱為多字節字符,或者窄字符。
有的編碼方式是固定長度的,不管字符編號大小,始終採用 n 個字節存儲,例如 UTF-32、UTF-16 等;如果一個字符使用了這種編碼方式,我們就將它稱為寬字符。
Unicode 字符集可以使用窄字符的方式存儲,也可以使用寬字符的方式存儲;GB2312、GBK、Shift-JIS 等國家編碼一般都使用窄字符的方式存儲;ASCII 只有一個字節,無所謂窄字符和寬字符。
引用: http://c.biancheng.net/view/v...