字元編碼是怎麼回事?

2020-07-16 10:04:21
在計算機中我們所處理的字元資訊,即文字資訊(包括數位、字母、文字、標點符號等)是以一種特定編碼格式來定義的。為了使世界各國的文字資訊能夠通用,就需要對字元編碼做標準化。

我們現在最常用也最基本的字元編碼系統是 ASCII 碼(American Standard Code for Information Interchange,美國資訊交換標準碼)。ASCII 碼定義每個字元僅佔一個位元組,可表示阿拉伯數位 0~9、26 個大小寫英文字母,以及我們現在在標準鍵盤上能看到的所有標點符號、一些控制字元(比如換行、迴車、換頁、振鈴等)。

ASCII 碼最高位是奇偶校驗位,用於通訊校驗,所以真正有編碼意義的是低 7 個位元,因此只能用於表示 128 個字元(值從 0~127)。由於 ASCII 是美國國家標準,所以後來國際化標準組織將它進行國際標準化,定義為了 ISO/IEC 646 標準。兩者所定義的內容是等價的。

ISO/IEC 646 對於英文系國家而言是基本夠用了,但是對於拉丁語系、希臘等國家來說就不夠用了。所以後來 ISO 組織就把原先 ISO/IEC 646 所定義字元的最高位也用上了,這樣就又能增加 128 個不同的字元,發布了 ISO/IEC 8859 標準。

然而,歐洲大陸雖小,但國家卻有數百個,128 種擴充套件字元仍然不夠用。因此後來就在 8859 的基礎上,引入了 8859-n,n 從 1~16,每一種都支援了一定數量的不同的字母,這樣基本能滿足歐美國家的文字表示需求。

當然,有些國家之間仍然需要切換編碼格式,比如 ISO/IEC8859-1 的語言環境看 8859-2 的就可能顯示亂碼,所以,還得切換到 8859-2 的字元編碼格式下才能正常顯示。

而在中國大陸,我們自己也定義了一套用於顯示簡體中文的字元集——GB2312。它在 1981 年 5 月 1 日開始實施,是中國國家標準的簡體中文字元集,全稱為《資訊交換用漢字編碼字元集·基本集》。

GB2312 收錄了 6763 個漢字,包括拉丁字母、希臘字母、日語假名、俄語和蒙古語用的西里爾字母在內的 682 個全形字元。

然後又出現了 GBK 字元集,GBK 1.0 收錄了 21886 個符號,其中漢字就包含了 21003 個。GBK 字元集主要擴充套件了繁體中文字。

由於像 GB2312 與 GBK 能表示成千上萬種字元,因此這已經遠超 1 個位元組所能表示的範圍。它們所採用的是動態變長位元組編碼,並且與 ASCII 碼相容。
  • 如果表示 ASCII 碼部分,那麼僅 1 個位元組即可,並且該位元組最高位為 0。
  • 如果要表示漢字等擴充套件字元,那麼頭 1 個位元組的最高位為 1,然後再增加一個位元組(即用兩個位元組)進行表示。

所以,理論上,除了第 1 個位元組的最高位不能動之外,其餘位元都能表示具體的字元資訊,因而最多可表示27 + 215 = 32896種字元。

當然,正由於 GB2312 與 GBK 主要用於亞洲國家,所以當歐美國家的人看到這些字元資訊時顯示的是亂碼,他們必須切換到相應的漢字編碼環境下看才能看到正確的文字資訊。為了能真正將全球各國語言進行互換通訊,出現了 Unicode(Universal Character Set,UCS)標準。它對應於編碼標準 ISO/IEC 10646。

Unicode 前後也出現了多個版本。早先的 UCS-2 採用固定的雙位元組編碼方式,理論上可表示 216 = 65536 種字元,因此極大地涵蓋了各種語言的文字元號。

不過後來,標準委員會意識到,對於像希伯來字母、拉丁字母等壓根就不需要用兩個位元組表示,而且定長的雙位元組表示與原有的 ASCII 碼又不相容,因此後來出現了現在用得更多的 UTF-8 編碼標準。

UTF-8 屬於變長的編碼方式,它最少可用 1 個位元組表示 1 個字元,最多用 4 個位元組表示 1 個字元,判別依據就是看第 1 個位元組的最高位有多少個 1。
  • 如果第 1 個位元組的最高位是 0,那麼該字元用 1 個位元組表示;
  • 最高 3 位是 110,那麼用 2 個位元組表示;
  • 最高 4 位是 1110,那麼用 3 個位元組表示;
  • 最高位是 11110,那麼該字元由 4 個位元組來表示。

所以 UTF-8 現在大量用於網路通訊的字元編碼格式,包括大多數網頁用的預設字元編碼也都是 UTF-8 編碼。

儘管 UTF-8 更為靈活,而且也與 ASCII 碼完全相容,但不利於程式解析。所以現在很多程式語言的編譯器以及執行時庫用得更多的是 UTF-16 編碼來處理原始碼解析以及各類文字解析,它與之前的 UCS-2 編碼完全相容,但也是變長編碼方式,可用雙位元組或四位元組來表示一個字元。

如果用雙位元組表示 UTF-16 編碼的話,範圍從0x0000到0xD7FF,以及從0xE000到0xFFFF。這裡留出0xD800到0xDFFF,不作為具體字元的編碼表示,而是用於四位元組編碼時的編碼替換。當UTF-16表示0x10000到0x10FFFF之間的字元時,先將該範圍內的值減去0x10000,使得結果落在0x00000到0xFFFFF範圍內。然後將結果劃分為高10位與低10位兩組。將低10位的值與0xDC00相加,獲得低16位元;高10位與0xD800相加,獲得高16位元。比如,一個Unicode定義的碼點(code point)為0x10437的字元,用UTF-16編碼表示的步驟如下。

1)先將它減去0x10000——0x10437-0x10000=0x0437。

2)將該結果分為低10位與高10位,0x0437用20位二進位制表示為00000000010000110111,因此高10位是0000000001=0x01;低10位則是0000110111,即0x037。

3)將高10位與0xD800相加,得到0xD801;將低10位與0xDC00相加,獲得0xDC37。因此最終UTF-16編碼為0xD801DC37。

我們看到,儘管UTF-16也是變長編碼表示,但是僅低16位元就能表示很多字元符號,況且即便要表示更廣範圍的字元,也只是第二種四位元組的表示方法,這遠比UTF-8四種不同的編碼方式要簡潔很多。因此,UTF-16用在很多程式語言執行時系統字元編碼的場合比較多。像現在的Java、Objective-C等程式語言環境內部系統所表示的字元都是UTF-16編碼方式。

另外,現在還有UTF-32編碼方式,這一開始也是Unicode標準搞出來的UCS-4標準,它與UCS-2一樣,是定長編碼方式,但每個字元用固定的4位元組來表示。不過現在此格式用得很少,而且HTML5標準組織也公開宣告開發者應當儘量避免在頁面中使用UTF-32編碼格式,因為在HTML5規範中所描述的編碼偵測演算法,故意不對它與UTF-16編碼做區分。