2017年8月2日 星期三

字元編碼系統 (Character Coding System) - 統一碼/標準萬國碼 (Unicode)

文本 (Text) 是由字詞 (Word) 與符號 (Symbol) 等所組成,是具有意義、可以閱讀理解的實體。而字詞 (包含數值) 則是由字母 (Letter) 與數字 (Digit) 所構成:大小寫英文共有 52 個不同的字母,常用的中文字母則約有 5000 個;數字則是由 0 到 9 的阿拉伯數字。與書寫文字不同,電腦在處理文本時的基本單位稱為字元 (Character),其包含了字母、數字、符號以及控制元等。

字元必需要經過適當的編碼系統 (Coding System) 處理後,才能夠讓以二進位制為運作基礎的電子式資通訊系統進行交換、處理、以及呈現。編碼系統會將每個字元編碼對映到固定的代碼點 (Code Point)。目前最常用的編碼系統為統一碼/標準萬國碼 (Unicode)。

在編碼系統的術語中,字元集 (Character Set)、編碼字元表 (Coded Character Set)、字元對映表 (Character Map)、代碼集 (Codeset)、以及代碼頁 (Code Page) 等術語,用來描述字元庫 (Character Repertoire) 以及如何將其編碼成代碼單元 (Code Unit) 的串流,讓每個字元對映到不重複的指定代碼點。但是,有些標準機構在編寫和統一不同的編碼系統時使用精確術語,讓這些術語變成了相關但是具有不同含義的術語。一般情況下,這些術語仍可互換使用,而最常被使用的名稱為字元集。

傳統的編碼系統 (例如,ASCII、ISO 8859-1、BIG 5等) 具有其侷限性,只能夠處理其編碼空間 (Codespace) 內有被對映到的字元 (也就是其字元庫僅包含部份特定的字元);如果編碼系統彼此之間不相容 (同一個字元的編碼值不同、或是字元庫不相容),在交換文件時就會發生亂碼的情況。Unicode 對世界上大部分的文字系統進行了整理與編碼,讓其能夠編碼絕大多數的字元 (在Unicode 標準 10.0 版本中收錄了來自 139 種語言以及符號集的 136,755 個字元);並規範了多種 Unicode 轉換格式 (Unicode Transformation Format, UTF) 對映方法 (Mapping Methods),讓字元在編碼、呈現以及處理時具有一致性。

在 Unicode 標準中,每一個平面 (Plane) 為具有連續 65,536 (216) 個代碼點的群組,受限於 UTF-16 編碼方法, Unicode 定義了 17 個 (編號 0~16) 平面。因此 Unicode 的代碼空間為 0x0 到 0x10FFFF (0x 為16進位制表示法)。在 17 個平面中,Plane 0 被稱為基本多語文平面 (Basic Multilingual Plane, BMP),包含了大部份的常用字元;Plane 1 ~ Plane 16 則被稱為補充平面 (Supplementary Planes) 。Unicode 標準 10.0 版本中有 6 個平面的代碼點有被對映到字元,其中有 4 個平面有被命名。

Unicode 的代碼空間為 21 位元 (5 位元平面編號 + 16 位元代碼點),每一個字元的代碼點數值是固定的。Unicode 在表達字元對應的代碼點時,會在「U+」之後接著一組十六進位數字來表示字元的代碼點 (範圍為 U+0000 ~ U+10FFFF)。而基本多語言平面 (BMP) 使用四個十六進位數字來表示兩個位元組的編碼 (U+0000 ~ U+FFFF),其中 U+4E00 ~ U+9FFF 為中日韓統一表意文字。

UTF-32

如果要直接使用 Unicode 的代碼點來交換,需要使用 4 個位元組來編碼 (其中前導的 11 位元固定為 0),每個 32 位元值都代表一個 Unicode 代碼點,並且與該代碼點的數值完全一致。這樣的對映方法被稱為 UTF-32。與變動長度的 UTF-8、UTF-16 等對映方法不同,UTF-32 編碼固定使用 4 位元組。在大多數文件檔案中,補充平面的字元非常罕見,因而造成了 UTF-32 在空間上的浪費,形成其主要的缺點。而 UTF-32 的主要優點是可以快速找到代碼點所對映的字元 (因為代碼點是可索引的,所以在代碼點序列中尋找到所要的代碼點是常數時間)。

UTF-16

為了避免空間的浪費,變動長度的 UTF-8、UTF-16 等對映方法被提出來。在 UTF-16 中,每一個代碼點被使用 1 個或是 2 個 16 位元的代碼單元編碼。其中:
  1. 常見的代碼點為 U+0000 ~ U+D7FF 以及 U+E000 ~ U+FFFF 直接使用 1 個  16 位元的代碼單元編碼,而且代碼點的數值等於編碼的數值;
  2. 輔助平面 (U+10000 ~ U+10FFFF) 的代碼點被編碼為一對16位元長的碼元,稱作代理對(Surrogate Pair);
  3. 為了 UTF-16 的代理機制,Unicode 永久保留了 U+D800 ~ U+DFFF 代碼點不使用。

UTF-16 使用 2 到 4 個位元組編碼 (代碼單元為 16 位元),UTF-32 則是固定 4 個位元組 (代碼單元為 32 位元);在大部份的通訊以及儲存協定是以位元組 (8 位元) 為單位的情況下,皆會碰到大端序 (Big Endian) 或是小端序 (Little Endian) 的位元組順序 (Endianness) 問題。因此需要在編碼的最前面再加上位元組順序記號 (Byte Order Mark, BOM)。
  1. 當 BOM 為 0x0000FEFF (4 個位元組) 時表示編碼方式為 UTF-32 BE (Big Endian);
  2. 當 BOM 為 0xFFFE0000 (4 個位元組) 時表示編碼方式為 UTF-32 LE (Little Endian);
  3. 當 BOM 為 0xFFFE (2 個位元組) 時表示使用 UTF-16 LE;
  4. 當 BOM 為 0xFEFF (2 個位元組) 時表示採用  UTF-16 BE;
  5. 當 BOM 為 0xEFBBBF (3 個位元組) 時表示採用 UTF-8 (雖然 Unicode 標準允許 UTF-8 編碼使用 BOM,但是 UTF-8 沒有位元組順序問題,因此不建議在 UTF-8 編碼中使用 BOM 標識)。
有些支援 UTF-8 的編譯器  (例如 Java 編譯器) 不支援具有 BOM 的 UTF-8 文件,會將 BOM 視為不合法字元,而無法編譯。要將原始碼存檔為不具有 BOM 的 UTF-8 文件,必須使用可存為「檔首無 BOM」的編譯器 (例如 NotePad++)。

相較於大部份的 Linux 系統使用的是 UTF-8 編碼,繁體中文 Windows 使用的編碼則是相容於 Big 5 的 MS950 代碼頁 (Code Page),在使用記事本編輯文件時,如果儲存的字元不在 Big5/MS950 編碼範圍內 (例如,犇、鍂、堃等字),會出現轉存為 Unicode 格式的提示;此時可以改用 Unicode、Unicode big endian 或是 UTF-8 等三個不同編碼格式來儲存檔案。其中,「Unicode」實際上使用的編碼方法是 UTF-16 LE;「Unicode big endian」實際採用的編碼方法是 UTF-16 BE;「UTF-8」將儲存為具有 BOM 的 UTF-8 文件。

UTF-8

近年來,UTF-8 已經成為全球資訊網 (World Wide Web, WWW) 主要的字元編碼。許多國際標準組織在制定標準時也都建議要能夠支援 UTF-8。例如:全球資訊網協會 (World Wide Web Consortium, W3C) 在 XML 跟 HTML 的推薦標準中使用 UTF-8 做為預設編碼;在 RFC 2277 中,網際網路工程任務組 (Internet Engineering Task Force, IETF) 則是要求所有的協定都需要能夠使用 UTF-8 字集 (Charset),其中 RFC 3629 提供了 UTF-8 的編碼規則。

UTF-8 是一種使用 8 位元代碼單元的可變長度字元編碼。其具有與 ASCII 向下相容,以及可以避免 UTF-32 與 UTF-16 因為位元組順序問題所產生的複雜性等特性。UTF-8 使用 1 到 4 個位元組為每個字元編碼 (資料來源 Wiki):

位元組數目 代碼點位元 代碼點起點 代碼點終點 第 1 個位元組 第 2 個位元組 第 3 個位元組 第 4 個位元組
1 7 U+0000 U+007F 0xxxxxxx
2 11 U+0080 U+07FF 110xxxxx 10xxxxxx
3 16 U+0800 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 21 U+10000 U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  1. 代碼點 U+0000 ~ U+007F (128個 US-ASCII 字元):使用 1 個位元組編碼,其中首位元固定為 0,x 部份填入7 位元的代碼點數值;
  2. 代碼點 U+0080 ~ U+07FF (附加符號的拉丁文、希臘文、西里爾字母、亞美尼亞語、希伯來文、阿拉伯文、敘利亞文等):使用 2 個位元組編碼,其中第 1 位元組的高階位元固定為 110,第 2 位元組的高階位元固定為10;
  3. 代碼點 U+0800 ~ U+FFFF (其他 BMP 中的字元,包含了大部分的常用字以及漢字等):使用 3 個位元組編碼,其中第 1 位元組的高階位元固定為 1110,第 2 以及第 3 位元組的高階位元固定為10;
  4. 代碼點U+10000 ~ U+10FFFF (補充平面的字元):使用 4 個位元組編碼,其中第 1 位元組的高階位元固定為 11110,第 2、第 3 以及第 4 位元組的高階位元固定為10。

UTF-8 的編碼步驟為:
  1. 由代碼點數值決定所需要的位元組個數 (上表中每一列在 第 1 個位元組是互斥的,也就是說每個字元只會屬於其中的一種編碼方式);
  2. 在每個位元組的高階位元填上其固定值;
  3. 在 x 記號處填上代碼點二進位制數值:將代碼點數值由最低位元開始依序 (由右至左) 填入最後 1 個位元組的最低位元,當位元組的 x 記號處都填滿時,移到上一個位元組,直到所有的 x 記號處都被填滿為止。

以 "書" 為範例,使用 UTF-8 編碼的過程如下:
  1. "書" 的代碼點為 U+66F8 (二進位制表示為 0110 0110 1111 1000),介於 U+0800 ~ U+FFFF 之間,根據上述的規則,需要使用 3 個位元組來編碼;
  2. 3 個位元組的固定值為 1110xxxx 10xxxxxx 10xxxxxx;
  3. 將代碼點數值 ( 0110 0110 1111 1000) 由最低位元開始依序填入最後 1 個位元組的最低位元,可以得到 1110xxxx 10xxxxxx 10111000,移到上一個位元組,再依序 (由較低位元至高位元) 填入剩餘的數值,得到 1110xxxx 10011011 10111000,移到上一個位元組,再依序 (由較低位元至高位元) 填入剩餘的數值,最後得到 11100110 10011011 10111000,以 16 進位制表示為 0xE69BB8。

UTF-8 的解碼步驟為:
  1. 初始設定一個全部為 0、長度為 21 位元的二進位數字;
  2. 根據要解碼的數值,決定有那些位元是由代碼點數值編碼而成;
  3. 將這些位元數值從最小位元到最高位元,由右至左替換步驟 1 的 0 值,最後得到數值就是字元的代碼點。


以 "11100110 10011011 10111000" 為範例,使用 UTF-8 解碼的過程如下:
  1. 初始數字為 00000 00000000 00000000;
  2. 11100110 10011011 10111000 的前導位元 1110 得到字元是由 3 個字元組編碼而成,所以第 1 位元組中的前導 1110、第 2 及第 3 位元組中的前導 10 是編碼時加入的固定值,可以移除,得到  0110 011011 111000 是代碼點數值;
  3. 將步驟 2 得到的數值由右至左置換步驟 1 的數值,可以得到  00000  0110 0110 1111 1000 或是 0x66F8,即代碼點的數值為 U+66F8。

MySQL 的 utf8 與 utf8mb4

早期 MySQL 的 utf8 (utfmb3) 編碼最多僅使用 3 個位元組,故僅能夠涵蓋 BMP 的字元 ,遇到罕見字或是繪文字 (Emoji) 等需要使用 4 個位元組編碼的字元時便會不敷使用,而無法儲存。在 MySQL 5.5.3 版本以後,引進使用 4 個位元組來編碼字元的 utf8mb4 編碼 (也就是標準的 UTF-8 編碼)。

在 utf8mb4 編碼有兩種不同的排列方式:utf8_general_ci 以及 utf8_unicode_ci。utf8mb4_unicode_ci 的排序跟比較是基於 Unicode 標準,可以正確地排序多種不同的語言;但是在排序及比較上效能較差。utf8mb4_general_ci 並沒有實作所有的 Unicode 排序法則,在某些情況下 (某些特殊的語言或字元) 會使得排序的結果並不是想要的結果;但是在排序及比較上會較為快速。例如,在 utf8mb4_unicode_ci 排序中, "ß" 字元的排序會是 "ss","Œ" 則是 "OE";這樣的結果是我們在排序時想要的結果;而 utf8mb4_general_ci 則使用當個字元 (可能分別使用 "s" 跟 "e")。在目前伺服器的運算效能普遍過剩下,建議要採用 utf8_unicode_ci (正確性大於效能)。

語詞彙表

以下的術語引用自 Unicode 官方網站的語詞彙表
  1. 字元 (Character):通常可分為圖形字元以及控制與格式字元兩類,可以定義為 (1) 書面語言中具有最小語意價值的元件;指的是抽象的意義以及 (或) 形狀,而不是具體的形狀 (The smallest component of written language that has semantic value; refers to the abstract meaning and/or shape, rather than a specific shape.)。(2) 抽象字元的同義詞 (Synonym for abstract character.)。(3) 用於 Unicode 字元編碼的基本編碼單元  (The basic unit of encoding for the Unicode character encoding.)。(4) 以中文為起源的字元的表意書寫元素的英文名稱 (The English name for the ideographic written elements of Chinese origin.)。
  2. 字元集 (Character Set):一個用於表現文本資訊的元素彙集 (A collection of elements used to represent textual information.)。
  3. 代碼空間 (Codespace):可用於編碼字元的數值範圍,在 Unicode 標準中,範圍從 0x0 到 0x0FFFF16 的整數值 (A range of numerical values available for encoding characters. For the Unicode Standard, a range of integers from 0x0 to 0x0FFFF.)。
  4. 代碼點 (Code Point):(1) 在 Unicode 碼空間的任何值,即範圍從 0x0 到 0x0FFFF 的整數值 (Any value in the Unicode codespace; that is, the range of integers from 0x0 to 0x0FFFF.)。不是所有的代碼點都會被指定到編碼字元 (Not all code points are assigned to encoded characters.)。 (2)  在編碼字元集中一個字元的值或是位置 (A value, or position, for a character, in any coded character set.)。
  5. 編碼字元集 (Coded Character Set, CCS):每一個字元都被指定到一個數值代碼點的一個字元集 (A character set in which each character is assigned a numeric code point.)。通常簡稱為字元集、字集、或碼集,或是使用字首縮寫 CCS (Frequently abbreviated as character set, charset, or code set; the acronym CCS is also used.)。
  6. 代碼頁 (Code Page): A coded character set, often referring to a coded character set used by a personal computer—for example, PC code page 437, the default coded character set used by the U.S. English version of the DOS operating system.
  7. 代碼單元 (Code Unit):可以表現用於處理或交換的編碼文本的單位的最小位元組合 (The minimal bit combination that can represent a unit of encoded text for processing or interchange.)。對於 UTF-8 來說,代碼單元是 8 位元;對於 UTF-16 來說,代碼單元是 16 位元;對於 UTF-32 來說,代碼單元是32位元。
  8. 編碼字完 (Coded/Encoded Character):抽象字元和代碼點之間的關聯 (或映射) (An association (or mapping) between an abstract character and a code point.)。 一個抽象字元自己本身並不具有數值,但是在編碼一個字元的過程會中聯結一個特定的字元點到一個特定的抽象字元,形成了一個編碼字元 (By itself, an abstract character has no numerical value, but the process of “encoding a character” associates a particular code point with a particular abstract character, thereby resulting in an “encoded character.”)。
  9. 字元庫或字彙 (Character Repertoire): 包含在一個字元集內的字元彙集 (The collection of characters included in a character set.


沒有留言: