轉(zhuǎn)自:https://cloud.tencent.com/developer/article/1470820
字符編碼
你是否認(rèn)為 ASCII 碼就是一個字符,一個字節(jié)就是一個字符,一個字符就是 8 比特?你是否認(rèn)為 UTF-8 就是用 8 比特表示一個字符?如果真的是這樣認(rèn)為這篇文章就很適合你。
為什么要有編碼?
首先大家需要明確的是在計算機里所有的數(shù)據(jù)都是字節(jié)的形式存儲和處理的。我們需要字節(jié)來表示計算機里的信息,但是這些字節(jié)本身又是沒有任何意義的。我們需要對這些字節(jié)賦予實際的意義,制定各種編碼標(biāo)準(zhǔn)。
編碼模型
首先需要知道的是存在兩種編碼模型
簡單字符集
在這種編碼模型里,一個字符集定義了這個字符集里包含什么字符,同時把每個字符如何對應(yīng)成計算機里的比特也進(jìn)行了定義。例如 ASCII,在 ASCII 里直接定義了 A -> 0100 0001。
現(xiàn)代編碼模型
在現(xiàn)代編碼模型里要知道一個字符如何映射成計算機里比特,需要經(jīng)過如下幾個步驟:
知道一個系統(tǒng)需要支持哪些字符,這些字符的集合被稱為字符表(Character repertoire)
給字符表里的抽象字符編上一個數(shù)字,也就是字符集合到一個整數(shù)集合的映射。這種映射稱為編碼字符集(CCS:Coded Character Set), unicode 是屬于這一層的概念,unicode 跟計算機里的什么進(jìn)制啊沒有任何關(guān)系,它是完全數(shù)學(xué)的抽象的。
將 CCS 里字符對應(yīng)的整數(shù)轉(zhuǎn)換成有限長度的比特值,便于以后計算機使用一定長度的二進(jìn)制形式表示該整數(shù)。這個對應(yīng)關(guān)系被稱為字符編碼表(CEF:Character Encoding Form)UTF-8, UTF-16 都屬于這層。
對于 CEF 得到的比特值具體如何在計算機中進(jìn)行存儲,傳輸。因為存在大端小端的問題,這就會跟具體的操作系統(tǒng)相關(guān)了。這種解決方案稱為字符編碼方案(CES:Character Encoding Scheme)。
平常我們所說的編碼都在第三步的時候完成了,并沒有涉及到 CES。所以 CES 并不在本文的討論范圍之內(nèi)。 現(xiàn)在也許有人會想為什么要有現(xiàn)代的編碼模型?為什么在現(xiàn)在的編碼模型要拆分出這么多概念?直接像原始的編碼模型直接都規(guī)定好所有的信息不行嗎?這些問題在下文的編碼發(fā)展史中都會有所闡述。
編碼的發(fā)展史
ASCII
ASCII 出現(xiàn)在上個世紀(jì) 60 年代的美國,ASCII 一共定義了 128 個字符,使用了一個字節(jié)的 7 位。定義的這些字符包括英文字母 A-Z,a-z,數(shù)字 0-9,一些標(biāo)點符號和控制符號。在 Shell 里輸入man ASCII,可以看到完整的 ASCII 字符集。ASCII 采用的編碼模型是簡單字符集,它直接定義了一個字符的比特值表示。例如上文提到的A -> 0100 0001。也就是 ASCII 直接完成了現(xiàn)代編碼模型的前三步工作。 在英語系國家里 ASCII 標(biāo)準(zhǔn)很完美。但是不要忘了世界上可有好幾千種語言,這些語言里不僅只有這些符號啊。如果使用這些語言的人也想使用計算機,ASCII 就遠(yuǎn)遠(yuǎn)不夠了。所以到這里編碼進(jìn)入了混亂的時代。
混亂時代
人們知道計算機的一個字節(jié)是 8 位,可以表示 256 個字符。ASCII 卻只使用了 7 位,所以人們決定把剩余的一位也利用起來。這時問題出現(xiàn)了,人們對于已經(jīng)規(guī)定好的 128 個字符是沒有異議的,但是不同語系的人對于其他字符的需求是不一樣的,所以對于剩下的 128 個字符的擴展會千奇百怪。而且更加混亂的是,在亞洲的語言系統(tǒng)中有更多的字符,一個字節(jié)無論如何也滿足不了需求了。例如僅漢字就有 10 萬多個,一個字節(jié)的 256 表示方式怎么能夠滿足呢。于是就又產(chǎn)生了各種多字節(jié)的表示一個字符方法(gbk 就是其中一種),這就使整個局面更加的混亂不堪。(希望看到這里的你不再認(rèn)為一個字節(jié)就是一個字符,一個字符就是8比特)。每個語系都有自己特定的編碼頁(code pages)的狀況,使得不同的語言出現(xiàn)在同一臺計算機上,不同語系的人在網(wǎng)絡(luò)上進(jìn)行交流都成了癡人說夢。這時 Unicode 出現(xiàn)了。
Unicode
Unicode 就是給計算機中所有的字符各自分配一個代號。Unicode 通俗來說是什么呢?就是現(xiàn)在實現(xiàn)共產(chǎn)主義了,各國人民不在需要自己特定的國家身份證,而是給每人一張全世界通用的身份證。Unicode 是屬于編碼字符集(CCS)的范圍。Unicode 所做的事情就是將我們需要表示的字符表中的每個字符映射成一個數(shù)字,這個數(shù)字被稱為相應(yīng)字符的碼點(code point)。例如“嚴(yán)”字在 Unicode 中對應(yīng)的碼點是 U+0x4E25。
到目前為止,我們只是找到了一堆字符和數(shù)字之間的映射關(guān)系而已,只到了CCS的層次。這些數(shù)字如何在計算機和網(wǎng)絡(luò)中存儲和展示還沒有提到。
字符編碼
前面還都屬于字符集的概念,現(xiàn)在終于到 CEF 的層次了。為了便于計算的存儲和處理,現(xiàn)在我們要把哪些純數(shù)學(xué)數(shù)字對應(yīng)成有限長度的比特值了。最直觀的設(shè)計當(dāng)然是一個字符的碼點是什么數(shù)字,我們就把這個數(shù)字轉(zhuǎn)換成相應(yīng)的二進(jìn)制表示,例如“嚴(yán)”在 Unicode 中對應(yīng)的數(shù)字是 0x4E25,他的二進(jìn)制是100 1110 0010 0101,也就是嚴(yán)這個字需要兩個字節(jié)進(jìn)行存儲。按照這種方法大部分漢字都可以用兩個字節(jié)來表示了。但是還有其他語系的存在,沒準(zhǔn)兒他們所使用的字符用這種方法轉(zhuǎn)換就需要 4 個字節(jié)。這樣問題又來了到底該使用幾個字節(jié)表示一個字符呢?如果規(guī)定兩個字節(jié),有的字符會表示不出來,如果規(guī)定較多的字節(jié)表示一個字符,很多人又不答應(yīng),因為本來有些語言的字符兩個字節(jié)處理就可以了,憑什么用更多的字節(jié)表示,多么浪費。
這時就會想可不可以用變長的字節(jié)來存儲一個字符呢?如果使用了變長的字節(jié)表示一個字符,那就必須要知道是幾個字節(jié)表示了一個字符,要不然計算機可沒那么聰明。下面介紹一下最常用的 UTF-8(UTF 是Unicode Transformation Format的縮寫)的設(shè)計。請看下圖(來自阮一峰的博客,博客地址:https://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html)
其中:x 表示可用的位

通過 UTF-8 的對應(yīng)關(guān)系可以把每個字符在Unicode 中對應(yīng)的碼點,轉(zhuǎn)換成相應(yīng)的計算機的二進(jìn)制表示??梢园l(fā)現(xiàn)按照 UTF-8 進(jìn)行轉(zhuǎn)換是完全兼容原先的 ASCII 的;而且在多字節(jié)表示一個字符時,開頭有幾個 1 就表示這個字符按照 UTF-8 轉(zhuǎn)換后由幾個字節(jié)表示。下面一個實例子來自阮一峰的博客。
已知“嚴(yán)”的unicode是4E25(100111000100101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800-0000 FFFF),因此“嚴(yán)”的UTF-8編碼需要三個字節(jié),即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,從“嚴(yán)”的最后一個二進(jìn)制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,“嚴(yán)”的UTF-8編碼是“11100100 10111000 10100101”,轉(zhuǎn)換成十六進(jìn)制就是0xE4B8A5。
注:【依次從后向前填入格式中的x】意思是,將“嚴(yán)”的二進(jìn)制表示從后往前,依次替代 x
除了 UTF-8 這種轉(zhuǎn)換方法,還存在 UTF-16,UTF-32 等等轉(zhuǎn)換方法。這里就不再多做介紹。(注意UTF后邊的數(shù)字代表的是碼元的大小。碼元(Code Unit)是指一個已編碼的文本中具有最短的比特組合的單元。對于 UTF-8 來說,碼元是 8 比特長;對于 UTF-16 來說,碼元是 16 比特長。換一種說法就是 UTF-8 的是以一個字節(jié)為最小單位的,UTF-16 是以兩個字節(jié)為最小單位的。)