[Character set & Encoding]Part1.字元編碼 ASCII, Unicode, UCS, UTF

What are ASCII, Unicode, UCS, UTF?

< Basic >

用歷史發生順序的角度來理解,我覺得挺好的。

先備知識:

電腦無法儲存那些我們人類識別的出來的東西,像是文字(letters), 數字(numbers), 圖片(pictures), 電腦完去無法儲存,電腦從始至終只能儲存bit並且拿來運作,而這個 bit 只會有兩種 value, true or false, 1 or 0, yes or no, 或是任何你認為可以代表這兩種值的名詞。並且電腦是通過「電」來運作,如果把「電」實體化(這裡只是我自己的比喻,專家請不要太認真,我只想幫助自己好理解電腦運作), 實際上「真正的 bit」就好像那些電碎片,時而出現,時而消失。而通常在電腦科學領域,我們常方便性的使用 10 去表現。所以一樣的編碼也是電腦科學領域,我們會常在文章裡看到 10

要認知的 2 件事實:

  1. 電腦最早是美國那裡發明的,所以當然一開始就只針對英語字母做設定
  2. 但軟體後來不是只有英語母語者在使用,而是世界上所有不同母語的人們做使用

那後來是怎麼做的..? 這裡就是重點

幾個名詞解釋:

  • encode: (verb) convert into a coded form.

  • code: (noun) a system of words, letters, figures, or other symbols substituted for other words, letters, etc.

  • character set, charset: The set of characters that can be encoded. 實際上常用在同義於 "字符集"

  • code page: A "page" of codes that map a character to a number or bit sequence. A.k.a. "the table". 實際上常用在同義於 "字符集"

  • string: A string is a bunch of items strung together. A bit string is a bunch of bits, like 01010011. A character string is a bunch of characters, like Hi, there. 在幾個情況會出現的同義為 "字串/符號/字符/sequence".

  • 假設目前有十進位數字 99,在不同進位下會長不一樣,但實際上是同一個東西。

    • Binary(二進位): 1100011 in binary
    • Octal(八進位): 143 in octal
    • Decimal(十進位): 99 in decimal
    • Hexadecimal(十六進位): 63 in hexadecimal

16進位補充:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 規律為: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
10進位 0 -> 16進位 00
10進位 9 -> 16進位 09
10進位 10 -> 16進位 0A
10進位 15 -> 16進位 0F
10進位 16 -> 16進位 10
10進位 25 -> 16進位 19
10進位 26 -> 16進位 1A
10進位 32 -> 16進位 20
10進位 48 -> 16進位 30
10進位 122 -> 16進位 7A
10進位 144 -> 16進位 90
10進位 160 -> 16進位 A0
10進位 256 -> 16進位 100

< 理解方向與順序 >

名詞似乎有點混亂:

應當從電腦科學的基本講起,當涉及到編碼我們必須了解字符集與編碼是兩件事。

在討論編碼時,我們可以拆成:

  1. 字符集(字元表/字元庫/字元列表/Character set/Charset/Character repertoire)
  2. 編碼(編碼方式/儲存方案/Encoding/Character encoding)

從這兩個面向去理解會比較清楚。

那這是什麼意思呢?

總體來說就是字符編碼(Character encoding)這件事我們要先知道今天是使用哪個字符集(想像成某種表)(Charset)去讓我們的符號對應表上定義的數字,然後我們再選用哪種編碼方案(Encoding)把表上對應到的數字透過當前編碼方案去儲存到電腦中。

討論順序:

  1. 不去探討久遠的 EBCDIC
  2. ASCII 的出現
  3. Unicode 的出現
  4. UCS-2, UCS-4
  5. UTF-8, UTF-16

< ASCII 的出現 >

「ASCII」(American Standard Code for Information Interchange),我覺得要了解編碼,大概可以從這裡開始。

因為要讓電腦儲存並處理文字,所以最早美國出現 ASCII 來解決。

1. 先討論字符集:

所謂字符集,就好像是用另一種形式呈現同樣的東西,在編碼裡我們常把符號對應到特定數字去代表這個符號,而在 ASCII 的字符集很直覺的把文字轉換成人類日常生活理解的十進位數字。舉例: "A" 變成十進位數字 65, 把 "B" 變成十進位數字 66, 把 "a" 變成十進位數字 97,把 "z" 變成十進位數字 122,而其實這樣的對應方式就像是有一張表,在表上清楚寫明所有文字對應的十進位數字是什麼,同時這個十進位數字稱作它的碼位(碼點)(像是一種編號),也可以稱作「ASCII Code」(有些是寫以二進位表示時是 ASCII Code)。

在 ASCII 有兩種版本:

  1. 第一種版本 ASCII : 從十進位數字 0 - 127,總共 128 個
  2. 第二種版本 EASCII: 從原本的 0 - 127, 往後再加 128 - 255,總共 256 個
  • 裡面的十進位數字內容大概像這樣:

    • 控制字符: 0 - 31 和 127
    • 可顯示字符(一些常用符號,包括數字符號0到9(非真正十進位數字,因為如果是十進位數字 0 到 9,就不用討論編碼了,直接轉成二進位即可,這邊是圖案文字的 0 到 9)): 32 - 64
    • 可顯示字符(大寫英文字母 A - Z): 65 - 90
    • 可顯示字符(一些符號): 91 - 96
    • 可顯示字符(小寫英文字母 a - z): 97 - 122
    • 可顯示字符(一些符號): 123 - 126
  • 再來是追加了後面的第二種版本也稱作 EASCII (Extended ASCII):

    • 擴充字符集(Extended Character Set)追加了一些擴充符號: 128 - 255

這邊也許會很混亂 ASCII Code 到底指的是上面說的十進位數字,還是以二進位表示也是 ASCII Code,還是以十六進位表示才算是 ASCII Code, 我覺得這邊只要理解都是同樣的東西,只是表示不同,舉個例子:

  • 字符 A 依照 ASCII 的字符集:
    • 在2進位會是: 01000001
    • 在10進位會是: 65
    • 在16進位會是: 41
    • 在 HTML 10進位的 Entity Number 會是: &#65; -> 試著在 charset=UTF-8 (UTF-8 涵蓋 ASCII,後面會說到)的 html page render <p>&#65;</p> 你會發現會顯示 A
    • 在 HTML 16進位的 Entity Number 會是: &#x0041; -> 也可以試試 <p>&#x0041;</p>,也可以顯示 A

參考這個截圖會了解: ASCII Table, linooohon.com

所以只是表示的問題,有時候當聽到 「A 是 65 」精確一點要說,A 在 ASCII 的規則下以十進位表示時是 65, 記得在不同進制下表示就會不一樣。

2. 再來是編碼方式:

ASCII的編碼方式很簡單,直接把對應的十進位數字轉成二進位交給電腦儲存與處理。 邏輯就像是:

  • A : ASCII Code(Decimal) 65 => Binary 01000001
  • B : ASCII Code(Decimal) 66 => Binary 01000010
  • a : ASCII Code(Decimal) 97 => Binary 01100001

所以基本 128 個的 ASCII Code 對應的二進位範圍會是: 0000 0000 ~ 0111 1111 : 可以發現只使用到 7 bits,左邊第一個一定是 0

擴充款的需要 256 個 ASCII Code 才會讓左邊第 8 bit 為 1。

3. ASCII 小總結:

ASCII 是什麼?

Answer: 一個早期出現的編碼規則(包括定義字符集與定義編碼方式),有一張表定義了符號如何轉換成十進位,基本款是 ASCII Code: 0 - 127,共 128 個符號而已,而編碼方式是把十進位直接轉成二進位最多就只需要 1 byte 去儲存在電腦。

特徵:

  1. 字符集: 有定義字符集(編碼表),讓每個符號有對應的 ASCII Code(十進位數字), 128個 或 256個。
  2. 編碼方式: 把 ASCII Code(十進位數字) 直接轉成二進位儲存,範圍是 7 bits 或 8 bits。
  3. 缺點: 支援符號非常有限,只能 Cover 到英語系字母。
  4. ASCII 是一個同時定義字符集,也定義編碼方式的編碼系統 (之所以這樣說是因為你會發現對於 Unicode 的實踐,後期的 Unicode 就偏向只是字符集,它必須搭配其他編碼方式,我們常用的 UTF-8 就是其中一種編碼方式,後面會說到)。

References:

< Unicode 的出現 >

之所以出現,是經過對 ASCII 的理解,會發現他能支援的符號非常有限,於是後來每個國家都開始對 ASCII 做擴充去支援自己語系,或是設計自己語系的編碼方式,這時候世界就有數不清的不同編碼方式,發生了一個問題那就是當網路愈來愈普及,資訊交流愈來愈頻繁,不同的編碼方式,會造成許多資料轉換的時間成本,溝通成本變高,於是 Unicode 就出現了,他幾乎把全世界的符號都包含進去,它定義了字符集(Character set), 但編碼方式卻有很多種,這個是要釐清的地方,所以當我們說 Unicode編碼,必須要知道我用的是 Unicode 字符集,但編碼方式必須再問清楚是用哪一套編碼方式去處理 Unicode?

在 Unicode 被廣泛使用之前,一開始是有兩個組織在試著建立全世界統一的字符集,分別是「國際標準化組織(ISO)出的 ISO 10646 和 「Unicode Consortium」出的 Unicode ,後來他們也有合作把兩者合併造就現在的 Unicode, 而 ISO 10646 還是有單獨存在的,只是後來 Unicode 比較被廣泛應用。

0. Unicode 字符集:

在 Unicode 字符集裡,所以符號都可以對應的一個特定數字,這個數字稱為碼點(碼位)(Code Point),這跟 ASCII 的 ASCII Code 是同一個概念,只是他們規則不同,在 ASCII 只有 128 個符號要處理,可以直接對應一個約定好的十進位數字就好,但是在 Unicode 因為需要處理全世界所有符號,它的做法是先把它分為17個平面(plane)(就很像上學分班(分群)的概念),每個平面包含 65536 個碼點(code point),所有整個 17 個平面的碼點(至今為止有定義的範圍)表示為:

U+xx0000 ~ U+xxFFFF ,其中 xx 表示16進制值從 00₁₆ - 10₁₆,共 17 個平面。

1. Unicode 是單純字符集,還是也包含編碼規則?

有些人覺得它只是字符集,有些人覺得它也包含對應的編碼規則,以我的理解, Unicode 後來因為 UTF-8 和 UTF-16 的出現(後面會提到),就比較像是一個字符集,只是在最一開始的時候的確比較像字符集與編碼規則一起(UCS-2, UCS-4 的話),它的編碼規則的確就像是 ASCII 的編碼規則一樣簡單,直接把對應的 code point(16進位)轉成2進位存到電腦裡,這也就是最一開始的編碼規則 UCS-2(由 ISO 提出),它的轉換方式是把 code point 都轉成 2 bytes,它涵蓋了 65536 個字符,也是後面會提到的 Unicode 的第一平面(BMP)。


但 UCS-2 的 code point 為 U+0000 ~ U+FFFF,共涵蓋 65536 個字符,發現所囊括的字符太少,於是乎後來又有 UCS-4,它的規則就是 code point 一定轉成 4 bytes,這時候也會發現不是每個字都要用到 4 bytes(原本英文字母在 ASCII 用 1 byte 用得好好的), 所以這個編碼規則也挺浪費的,所以之後也出現其他種編碼方式,底下講一下各個實現 Unicode 的編碼方式。

2. 實踐 Unicode編碼 常見出現形式:

  • 以 U+開頭,後面接 16進位 4碼Code Point, 或 6碼Code Point
  • 以 0x開頭, 後面接 16進位 4碼Code Point, 或 6碼Code Point
  • 以 \x開頭,後面接 16進位 2碼Code Point,並多個連續出現,一個 \x 通常就代表是 1 byte

3. 實現 Unicode 字符集的編碼方式/儲存方式

UCS-2:

  • 屬於固定長的編碼格式
  • 編碼範圍:
`U+0000` ~ `U+FFFF`
  • 等於是字符集與編碼合在一起:
    • 字符集: 這時候 Unicode字符集 就是只有 65536 個, U+0000 ~ U+FFFF
    • 編碼方式: 這時候的編碼方式也是直接 U+xxxx , 直接轉二進制儲存

UCS-2 相對於其它編碼方式算是最早期實現 Unicode 的編碼方式。 ISO 出的 2 bytes 編碼方式,可以編 65536 個大部分世界常用的字符,他不能包含完整的 Unicode 字符集,也因此後來出現了 UTF-16,它涵蓋了 UCS-2 可以處理的 Unicode 第一平面(Basic Multilingual Plane,簡稱 BMP) 的 65536 個 code point,但同時也支援其他平面的編碼(Supplementary Planes),所以可以說 UTF-16 可以完整支援 Unicode 所有碼點的編碼。

UCS-4:

  • 屬於固定長的編碼格式
  • 編碼範圍:
`U+00000000` ~ `U+7FFFFFFF`
  • 等於是字符集與編碼合在一起:
    • 字符集: 這時候 Unicode字符集就是它定義的 U+00000000 ~ U+7FFFFFFF
    • 編碼方式: 這時候的編碼方式也是直接 U+xxxxxxxx , 直接轉二進制儲存

因為 UCS-2 只包含第一平面,有所不足而出現。 ISO 出的 4 bytes 編碼方式, 從 U+00000000 ~ U+7FFFFFFF,可以編 20多億個字符,但後來發現實際使用範圍不會超過 U+10FFFF,所以後面 UTF-32 出現了,它就只會到 U+0010FFFF ,所以可以說 UTF-32 是 UCS-4 的子集,

UTF-16:

  • 屬於可變長的編碼格式 如上面所說,它涵蓋 UCS-2 的 2 bytes 編碼方式,但也支援其餘平面的編碼,所以 UTF-16 的編碼範圍是 2 bytes 或 4 bytes。

2 bytes 時直接轉轉二進位, 4 bytes 時需像 UTF-8 一樣做編碼轉換:

  • 規則1. 2 bytes 時直接轉二進位 0x0000 0000 - 0x0000 FFFF xxxxxxxx xxxxxxxx
  • 規則2. 4 bytes 時要經過再次編碼 0x0001 0000 - 0x0010 FFFF 110110xx xxxxxxxx 110111xx xxxxxxxx

UTF-32:

  • 屬於固定長的編碼格式 如上面所說,它是 UCS-4 的子集,也就是說它也能涵蓋所有 Unicode 的編碼,但是它的編碼範圍永遠使用 4 bytes,規則就是直接把碼位轉成二進位,不足 4 bytes, 就補滿 0 就對了

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx

ex: A -> U+0041 以 UTF-32 來說就是 0000 0041 轉成二進位就是 1000001 那 UTF-32 下,就會以下面這個形式存進去電腦

00000000 00000000 00000000 01000001

UTF-8:

  • 屬於可變長的編碼格式 之所以出現,你會發現 UCS-2 支援不足; UCS-4 和 UTF-32 都固定需要 4 bytes 太浪費了; UTF-16 最少也需要 2 bytes,會發現這些編碼對於原本就只需要 1 byte 的 ASCII 編碼的人們而言太浪費空間了,所以 UTF-8 出現了,它兼容 ASCII 的編碼,屬於 ASCII 的編碼就照舊使用 1 byte,超過再來使用 2 bytes 到 4 bytes,所以 UTF-8 的編碼範圍從 1 byte - 4 bytes

UTF-8 第一個規則: 對於碼點轉換成二進位後只有 1 byte 的符號,編碼方式與 ASCII 一樣

UTF-8 第二個規則: 對於 n bytes 的符號(n > 1),第1個 byte 的前 n 位都設為 1, 第 n + 1 位設為 0,後面 byte 的前兩位一律都設為 10,最後再把沒有補上的 bit 由碼點轉換後的二進位由最右邊依序補上。


對應的四個區間如下,基本 UTF-8 會把 code point(16進制) 以 4 bytes(UCS-4, UTF-32 的方式)去看然後來轉換達到伸縮自如的 1 byte - 4 bytes 的彈性空間:

碼點: 0000 0000 ~ 0000 007F -> 轉換後為 1 byte,所以會是 0xxxxxxx

碼點: 0000 0080 ~ 0000 07FF -> 轉換後為 2 bytes,所以會是 110xxxxx 10xxxxxx

碼點: 0000 0800 ~ 0000 FFFF -> 轉換後為 3 bytes,所以會是 1110xxxx 10xxxxxx 10xxxxxx

碼點: 0001 0000 ~ 0010 FFFF -> 轉換後為 4 bytes,所以會是 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

可能還有點模糊:直接給一個中文字的 UTF-8 舉例

  • 記得 UTF, UCS 都是在 Unicode 的基礎上
  • 都用 \u, U+, 0x, \x, 搭配 16 進位

ex:

  1. 的 code point 是 0x0000 738B
  2. 轉成二進制是: 0000 0000 0000 0000 0111 0011 1000 1011
  3. 並且發現 的 code point 是在第三區間 0000 0800 ~ 0000 FFFF
  4. 於是寫下第三區間的編碼樣式 1110xxxx 10xxxxxx 10xxxxxx
  5. 接著把 step.2 的二進制形式與 step.4 的第三區間編碼樣式融合,填滿叉叉,從右到左
  6. 最後結果為 1110 0111 1000 1110 1000 1011
  7. 轉成16進制看一下(通常在寫程式都會看到\x開頭的16進制表示), 0xe7 0x8e 0x8b
1
2
3
print('王'.encode('utf-8'))
# 印出
# b'\xe7\x8e\x8b'

ASCII Table, linooohon.com

我的姓氏 "林" 的話就會是這樣: ASCII Table, linooohon.com

< The End >

總節:

  1. 編碼方式都是不經過再次編碼處理,直接從 Code Point 轉成二進位的有:

    • ASCII
    • UCS-2
    • UCS-4
    • UTF-16 在 2bytes 的時候
    • UTF-32 (不夠會補滿到 4 bytes)
  2. 會經過判斷再次編碼調整的有:

    • UTF-8
    • UTF-16 在 4bytes 的時候
  3. UCS(Universal Character Set), UTF(Unicode/UCS Transformation Format),兩者其實都是在實踐 Unicode,UCS 的概念其實偏像字符集,因為UCS的編碼就只是直接轉二進位,但廣義來說,也都是一種編碼方式,所以在這篇我都是歸在編碼方式,字符集就維持一種 Unicode,比較細的可以自行研究。

  4. UTF-8 在 2021 年的現在非常常見,因為它是可變長度也無痛兼容 ASCII。

  5. 中文在 UTF-8 編碼下需要 3 bytes,所以有時看到三個連續 \x** \x** \x** 這樣的東西,就可以很快的警覺有可能是處理資料時有中文字!

  6. 而相對來說繁體中文的 Big5編碼 只需要 2 bytes, 中國的GB編碼 也只需要 2 bytes

補充細節可以自行研究:

  1. BOM(Byte Order Mark)讓編碼方式可以細分為:
    • UTF-32BE
    • UTF-32LE
    • UTF-16BE
    • UTF-16LE
    • UTF-8 No BOM
    • UTF-8 with BOM

ASCII Table, linooohon.com

  1. UTF-16 在 4 bytes 的處理有「前導代理」(Leading Surrogate)與「後尾代理」(Trail Surrogate)的規則細節。

下篇寫一些在 Code 上面的情況與轉換。

Self Checklist:

  • What is Encoding, Character Encoding, Decoding.
  • What is Binary, Decimal, Hexadecimal, Octal System.
  • What is ASCII.
  • What is the difference between ASCII, Unicode, UCS and UTF.
  • What is the difference between Character set, Code Point and Encoding Process.

References:

< 下一篇Part2 > [Character set & Encoding]Part1.字元編碼 ASCII, Unicode, UCS, UTF