簡單聊聊字元編碼 Character Encoding

・11min

#「文字」是如何在電腦上面顯示的?

在電腦上,所有的東西都會被轉成 1 還有 0,文字也不例外。

文字在電腦上儲存的方式沒有想像的複雜,基本上也只是就是一個數字對一個字,想像一下

依照這個規律,如果我們要將 duck 這個英文單字編碼,那就會是

d: 4
u: 21
c: 3
k: 11

這種將文字轉換成某個集合的方式就稱為字元編碼(character encoding)。上面範例編碼方式將英文的小寫字母轉換成數字,數字又可以在依照不同的需要使用不同的格式(例如十進位或者是二進位)。

#ASCII

了解了什麼是編碼,我們可以看一下 ASCII ,一種被廣泛使用的編碼方式。下面是 ASCII 的 encoding table

一樣用剛剛的 duck 來作範例,當你使用 ASCII 編碼時,會被轉換成

d: 100
u: 117
c: 99
k: 107

轉換成常見的十六進位則是

d: 0x64
u: 0x75
c: 0x63
K: 0x6B

ASCII 有幾個特點

控制字元是什麼?

控制字元並非顯示的文字或符號,而是對裝置下的指令。像是 換行(Line feed, LF)、刪除 (DEL) 等等。這些控制字元在過去對不同的裝置(像終端機, 印表機等)有各自不同的用處,但現在大部分不再被使用。

#Terminology

了解了 ASCII,就可以說是了解字元編碼的基礎了。那再更進一步之前,可以先來聊聊一些術語以及定義。

#Character Encoding 字元編碼

Encoding 是一個很廣泛的概念,基本上把一套資訊轉換成另外一個形式的行為就可以稱作 encoding 編碼。不只是剛剛提到的文字轉換成數字,包含音訊、視訊等也都可以進行編碼。

在這篇文章則專注在 Character encoding,將 Character (字元)轉換成計算機可使用、傳遞的格式。

#那什麼是 character(字元)

在這裡的 character 基本上是參考 Unicode standard 中的解釋

Characters are the abstract representations of the smallest components of written language that have semantic value.

字元是書寫文字中具有語意的最小組成的抽象表現

The Unicode Standard, Version 15.0 - 2.2 Unicode Design Principles - Characters, Not Glyphs

標準中的敘述有點抽象,但可以從幾個方向去思考什麼是字元。

在不同的時代,同樣一個 「人」字,可能有不同的書寫方式,但這些不同的書寫方式都表達了同樣的 「人」這個意思,所以不同朝代的人並不算不同的字元

反過來,有沒有即使書寫方式相同,卻是被認為是不同字元的案例?

舉個例子,希臘文的 Σ 與數學符號的求和符號 ∑ (summation)兩者雖然書寫方式一樣,而且求和符號也的確是源自於希臘文的 Σ,但兩者使用至今已有不同的意涵,所以會被視為不同的字元。

話雖如此,上面都只是很簡單的舉例,但是事實上在 CJK (中文、日文、韓文中皆使用漢字,在這一議題上會簡稱為 CJK )中,同一個文字在不同的文化中都不同,甚至還有異體字。最後這些也都被 Unicode 收錄為不同的字元

但撇除複雜的案例,可以這樣理解:Character 字元是以意義為單位,來作為文字編碼、使用的最小元素

#什麼是 code point?

code point (中文譯做碼點1)表示在一套編碼中有多少的位置能夠用來表示字符。在剛剛的 ASCII 中就有 128 個 code points,而 Latin1 編碼則使用 8 個 bit ,也就是共 256 個 code points。而字元編碼中字集 character set 所處理的就是將字元對應到各自的碼點上。

#Code point 的位置就代表實際儲存的方式嗎?

非也,Code point 只是所謂的「位置」。實際儲存要考慮更多東西,大部分的編碼方式並不會直接以 code point 的位置儲存。例如 Unicode 在標準中,雖然字元對應到統一的 code point,但提供了三種不同的編碼方式:UTF-8, UTF-16, UTF-32。

#Wide characters 以及 Multibyte Characters

剛剛提到的 ASCII 是會使用 7 bits 的空間,但在大部分的系統中,都會使用 8 bit(使用 8 bit 非 7bit 會多出很多好處,存取記憶體時通常以 8bit 為單位),也就是一個 byte 來儲存一個 ASCII 的符號。

事實上當然不可能用了 8bit,卻只放入 7bit 的 ASCII。有個常用的編碼標準是 Latin1 正使用了一個 byte 來作編碼。Latin1 完全相容 ASCII,並且擴充了幾個帶有變音符的字母,可以滿足大部分拉丁語族(也就是普遍認知的西方語言)的需要。

但即使是使用了 1 個 byte,也僅僅只有 256 個 code points,對於亞洲國家的語言是完全不夠的。於此,有兩個作法(這兩個作法在 C 語言的標準中被明確的定義)可以解決這個問題,分別是 Wide characters (寬字元)以及 Multibyte Characters。

#Wide characters (寬字元)

這是一個很直覺的方法:

如果一個 byte 不夠,那就兩個

Wide characters 代表著

既然 8bit (1byte) 只能表示 256 個,那我們可以使用兩個 bytes ,那就能夠表示 65536 個字。

#UCS-2

了解了 Wide characters,我們來看看其中一個 Wide characters 的編碼標準 - UCS-2

UCS-2 使用 2 個 bytes 來作編碼,例如 剛剛舉的範例,在 ucs-2 中的編碼會是(以 Hex 16 進位來表示,以 byte 為單位):

d: 0x00 0x64
u: 0x00 0x75
c: 0x00 0x63
K: 0x00 0x6B

那如果是更複雜一點的狀況呢?舉個例子,中文的 「鴨子」

鴨:0x9d 0x28
子:0x5b 0x50

既然同上所述,UCS-2 使用了兩個 bytes,所以 UCS-2 的 charset 可以表達總共 65536 個字,鴨子也不是生僻字,因此在 UCS-2 中完全沒問題。

那在到下一種文字之前,我們先插播一段愛在西元前

我給妳的愛寫在西元前 深埋在美索不達米亞平原
用楔形文字刻下了永遠 那已風化千年的誓言 一切又重演

歌詞裡面提到楔形文字,那如果我要用楔形文字表達鴨子呢?UCS-2 無法表示楔形文字,因為楔形文字已經不再 UCS-2 能表示的字元中(UCS-2 僅能表示 Unicode 中 BMP 的字)

#Multibyte Characters

而另外一個作法比較特別,不一定要使用同樣的長度來表達每一個字,不同的字元會儲存成不同的長度

舉個例子,當表達 Latin1 可以表達的字母時,我們只需要使用 1 個 byte 來表示就好,但當需要表示例如漢字或者日文?就可以使用 2 byte 來表示

#UTF-8

上面同樣的範例,我們可以用 UTF-8 來表示看看差異

d: 0x64
u: 0x75
c: 0x63
K: 0x6B
鴨:0xE9 0xB4 0xA8
子:0xE5 0xAD 0x90

最後是楔形文字(以 Old Persian 書寫系統表示)

𐎣:0xF0 0x90 0x8E 0xA3
𐎸:0xF0 0x90 0x8E 0xB8
𐎢:0xF0 0x90 0x8E 0xA2
𐎪:0xF0 0x90 0x8E 0xAA

其中最大的差異就是,就如剛剛提到以 Multibyte Characters 方式編碼的 UTF-8 每個字元的編碼並不一定相同長度,而 UCS-2 則是每個字都相同。除此之外 UTF-8 可以表示褉型文字,但 UCS-2 不行。

#Wide character 以及 Multibyte character 的優缺點

了解兩者差異,可以簡單來聊聊兩者的優缺點

綜上所述,可以得到比較簡單的結論:

#Unicode 還有 Basic Multilingual Plane

剛剛有提到一個東西是 BMP (Basic Multilingual Plane),這是什麼?

常提到的 Unicode 通常指的是 Unicode 聯盟所訂定的 Unicode standard,這個標準包含了所有在計算機上表示文字的所有要素。我們可以關注的有兩個部分

Unicode 目前允許從 0x0 至 0x10FFFF 個 code points(碼位)。如果全部使用的話,總共可以儲存 1114111 個字。

當然,目前並沒有被完全使用,截至 2023 年九月收錄了總共有 149,813 個字4,而這些字被放在不同的區域,這些區域也就是所謂的 Plane (平面)5。一個 Plane 包含了 65535 個 code points,在 Unicode 中定義了 17 個 Plane。

平面範圍中文名英文名
0號0x00000xFFFF基本多文種平面Basic Multilingual Plane,簡稱BMP
1號0x100000x1FFFF多文種補充平面Supplementary Multilingual Plane,簡稱SMP
2號0x200000x2FFFF表意文字補充平面Supplementary Ideographic Plane,簡稱SIP
3號0x300000x3FFFF表意文字第三平面Tertiary Ideographic Plane,簡稱TIP
4號

13號
40000DFFFF(未啓用)
14號0xE00000xEFFFF特別用途補充平面Supplementary Special-purpose Plane,簡稱SSP
15號0xF00000xFFFFF保留作為私人使用區(A區)Private Use Area-A,簡稱PUA-A
16號0x10000010FFFF保留作為私人使用區(B區)Private Use Area-B,簡稱PUA-B

搬運自 wiki6

每個不同的平面包含了不同語言的文字,其中值得關注的是 BMP 以及 SMP。

前面提到 UCS-2 因為使用 2 個 byte ,所以只能表示 65535 個字。這正是 Unicode 中 BMP 的範圍:從 0x0000 至 0xFFFF 。而剛剛提到的楔形文字以及現在流行的 Emoji 則是在 SMP (多文種補充平面) 中,是無法用單純的 UCS-2 來表示的。

除了 Plain 之外,每個 Plain 之中還有實際依照不同文字系統來區分的 Block,例如注音符號就位於 0x31000x312F 的 Unicode Block “Bopomofo” 以及 U+31A0U+31BF Unicode Block “Bopomofo Extended” 7

前面把 UCS-2 講的很一文不值,但真的是這樣嗎?

#UTF-16 與 UCS-2

事實上,大部分人都不會接觸到純正的 UCS-2 編碼了8。Unicode 官方提出的有三種編碼9

而其中 UTF-16 則能夠完整兼容前面提到 UCS-2 編碼。再一次舉剛剛的例子

d: 0x00 0x64
u: 0x00 0x75
c: 0x00 0x63
K: 0x00 0x6B
鴨:0x9d 0x28
子:0x5b 0x50

最後是「楔形文字」的鴨子,在 UTF-16 中的表示是

𐎣:0xD8 0x00 0xDF 0xA3
𐎸:0xD8 0x00 0xDF 0xB8
𐎢:0xD8 0x00 0xDF 0xA2
𐎪:0xD8 0x00 0xDF 0xAA

作為一種 Multibyte character,UTF-16 的 code points 能夠表示超出 BMP 範圍的文字(包含了 1112064 個 code points)。這也是 UTF-16 可以成為廣泛兼容標準的一個因素。

#UTF-32

UTF-32 和 UTF-8、UTF-16 不同,則使用 Wide characters,所有的字元都是同樣的寬度。同樣的範例在 UTF-32 中的表示法如下

d: 0x00 0x00 0x00 0x64
u: 0x00 0x00 0x00 0x75
c: 0x00 0x00 0x00 0x63
K: 0x00 0x00 0x00 0x6B
鴨:0x00 0x00 0x9d 0x28
子:0x00 0x00 0x5b 0x50

最後是「楔形文字」的鴨子,在 UTF-16 中的表示是

𐎣:0x00 0x01 0x03 0xA3
𐎸:0x00 0x01 0x03 0xB8
𐎢:0x00 0x01 0x03 0xA2
𐎪:0x00 0x01 0x03 0xAA

#Conclusion

這篇文章對字元編碼進行了很基本的介紹,下一篇會更深入介紹一些編碼標準、以及一些源自於編碼的字串處理問題。

#Footnotes

  1. NAER Web of Words : 碼點

  2. HTML Standard

  3. RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content

  4. 來自 Unicode 15.1.0

  5. Glossary - plane

  6. Unicode字符平面映射 - 维基百科,自由的百科全书

  7. Unicode Block “Bopomofo”, Unicode Block “Bopomofo Extended”

  8. Unicode - Wikipedia : UCS-2 is an obsolete subset of UTF-16

  9. UnicodeStandard-15.0 2.5 Encoding Forms

# Comments

太精彩了,原來有一個組織在為工程師的和平努力。

9/5/2024, 10:07:36 AM