字元編碼的奧秘

字元編碼

你是否認為 ASCII 碼等於一個字元,你是否認為一個位元組就是一個字元,一個字元就是 8 比特,你是否還認為你是否還認為 UTF-8 就是用 8 比特表示一個字元?如果真的是這樣認為認真讀完這篇文章吧!

為什麼要有編碼?

首先大家需要明確的是在計算機里所有的數據都是位元組的形式存儲,處理的。我們需要這些位元組來表示計算機里的信息。但是這些位元組本身又是沒有任何意義的,所以我們需要對這些位元組賦予實際的意義。所以才會制定各種編碼標準。

編碼模型

首先需要明確的是存在兩種編碼模型

簡單字符集

在這種編碼模型里,一個字符集定義了這個字符集里包含什字元,同時把每個字元如何對應成計算機里的比特也進行了定義。例如 ASCII,在 ASCII 里直接定義了 A -> 0100 0001。

現代編碼模型

在現代編碼模型里要知道一個字元如何映射成計算機里比特,需要經過如下幾個步驟。

  1. 知道一個系統需要支持哪些字元,這些字元的集合被稱為字元表(Character repertoire)
  2. 給字元表裡的抽象字元編上一個數字,也就是字符集合到一個整數集合的映射。這種映射稱為編碼字符集(CCS:Coded Character Set),unicode 是屬於這一層的概念,跟計算機里的什麼進位啊沒有任何關係,它是完全數學的抽象的。
  3. 將 CCS 里字元對應的整數轉換成有限長度的比特值,便於以後計算機使用一定長度的二進位形式表示該整數。這個對應關係被稱為字元編碼表(CEF:Character Encoding Form)UTF-8, UTF-16都屬於這層。
  4. 對於 CEF 得到的比特值具體如何在計算機中進行存儲,傳輸。因為存在大端小端的問題,這就會跟具體的操作系統相關了。這種解決方案稱為字元編碼方案(CES:Character Encoding Scheme)。

平常我們所說的編碼都在第三步的時候完成了,都沒有涉及到CES。所以CES並不在本文的討論範圍之內。

現在也許有人會想為什麼要有現代的編碼模型?為什麼在現在的編碼模型要拆分出這麼多概念?直接像原始的編碼模型直接都規定好所有的信息不行嗎?這些問題在下文的編碼發展史中都會有所闡述。

編碼的發展史

ASCII

ASCII 出現在上個世紀 60 年代的美國,ASCII 一共定義了128 個字元,使用了一個位元組的 7 位。定義的這些字元包括英文字母 A-Z,a-z,0-9,一些標點符號和控制符號。在 Shel l里輸入 man ASCII,可以看到完整的 ASCII 字符集。ASCII 採用的編碼模型是簡單字符集,它直接定義了一個字元的比特值表示。里例如上文提到的 A -> 0100 0001。也就是ASCII直接完成了現代編碼模型的前三步工作。

在英語系國家裡 ASCII 標準很完美。但是不要忘了世界上可有好幾千種語言,這些語言里不僅只有這些符號啊。如果使用這些語言的人也想使用計算機,ASCII就遠遠不夠了。到這裡編碼進入了混亂的時代。

混亂時代

人們知道計算機的一個位元組是 8 位,可以表示 256 個字元。ASCII 卻只使用了 7 位,所以人們決定把剩餘的一位也利用起來。這時問題出現了,人們對於已經規定好的128 個字元是沒有異議的,但是不同語系的人對於其他字元的需求是不一樣的,所以對於剩下的 128 個字元的擴展會千奇百怪。而且更加混亂的是,在亞洲的語言系統中有更多的字元,一個位元組無論如何也滿足不了需求了。例如僅漢字就有 10 萬多個,一個位元組的 256 表示方式怎麼能夠滿足呢。於是就又產生了各種多位元組的表示一個字元方法(gbk 就是其中一種),這就使整個局面更加的混亂不堪。(希望看到這裡的你不再認為一個位元組就是一個字元,一個字元就是8比特)。每個語系都有自己特定的編碼頁(code pages)的狀況,使得不同的語言出現在同一台計算機上,不同語系的人在網路上進行交流都成了痴人說夢。這時 Unicode 出現了。

Unicode

Unicode 就是給計算機中所有的字元各自分配一個代號。Unicode 通俗來說是什麼呢?就是現在實現共產主義了,各國人民不在需要自己特定的國家身份證,而是給每人一張全世界通用的身份證。Unicode 是屬於編碼字符集(CCS)的範圍。Unicode所做的事情就是將我們需要表示的字元表中的每個字元映射成一個數字,這個數字被稱為相應字元的碼點(code point)。例如「嚴」字在 Unicode 中對應的碼點是U+0x4E25。

到目前為止,我們只是找到了一堆字元和數字之間的映射關係而已,只到了 CCS 的層次。這些數字如何在計算機和網路中存儲和展示還沒有提到。

字元編碼

前面還都屬於字符集的概念,現在終於到 CEF 的層次了。為了便於計算的存儲和處理,現在我們要把哪些純數學數字對應成有限長度的比特值了。最直觀的設計當然是一個字元的碼點是什麼數字,我們就把這個數字轉換成相應的二進位表示,如「嚴」在Unicode 中對應的數字是 0x4E25,他的二進位是 100 1110 0010 0101,也就是嚴這個字需要兩個位元組進行存儲。按照這種方法大部分漢字都可以用兩個位元組來表示了。但是還有其他語系的存在,沒準兒他們所使用的字元用這種方法轉換就需要 4 個位元組。這樣問題又來了到底該使用幾個位元組表示一個字元呢?如果規定兩個位元組,有的字元會表示不出來,如果規定較多的位元組表示一個字元,很多人又不答應,因為本來有些語言的字元兩個位元組處理就可以了,憑什麼用更多的位元組表示,多麼浪費。

這時就會想可不可以用變長的位元組來存儲一個字元呢?如果使用了變長的位元組表示一個字元,那就必須要知道是幾個位元組表示了一個字元,要不然計算機可沒那麼聰

明。下面介紹一下最常用的 UTF-8(UTF是Unicode Transformation Format的縮寫)的設計。請看下圖(來自阮一峰的博客)

x表示可用的位

通過 UTF-8 的對應關係可以把每個字元在 Unicode 中對應的碼點,轉換成相應的計算機的二進位表示。可以發現按照 UTF-8 進行轉換是完全兼容原先的 ASCII 的;而且在多位元組表示一個字元時,開頭有幾個 1 就表示這個字元按照 UTF-8 轉換後由幾個位元組表示。下面一個實例子來自阮一峰的博客

已知「嚴」的unicode是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),因此「嚴」的UTF-8編碼需要三個位元組,

即格式是「1110xxxx 10xxxxxx 10xxxxxx」。然後,從「嚴」的最後一個二進位位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,「嚴」的UTF-8編碼>是「11100100 10111000 10100101」,轉換成十六進位就是0xE4B8A5。

除了 UTF-8 這種轉換方法,還存在 UTF-16,UTF-32等等轉換方法。這裡就不再多做介紹.(注意UTF後邊的數字代表的是碼元的大小。碼元(Code Unit)是指一個已編碼的文本中具有最短的比特組合的單元。對於UTF-8來說,碼元是8比特長;對於UTF-16 來說,碼元是 16 比特長。換一種說法就是 UTF-8 的是以一個位元組為最小單位的,UTF-16 是以兩個位元組為最小單位的。)

結束語

花了兩天時間終於寫完了,相信看到這裡大家對於字元編碼有了較為清楚的認識,當然文章中肯定存在不準確之處,希望大家批評指正。

參考資料

字元編碼

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

字元編碼筆記:ASCII,Unicode和UTF-8

字符集和字元編碼

Windows 記事本的 ANSI、Unicode、UTF-8 這三種編碼模式有什麼區別?

如何向非技術人員解釋 Unicode 是什麼

字元編解碼的故事(ASCII,ANSI,Unicode,Utf-8)


推薦閱讀:

TAG:字符编码 | Unicode统一码 | Python |