National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 78 24°C PM2.5 18
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 06

文字編碼:ASCII、Unicode 與 UTF-8

字元怎麼變成位元組、亂碼是怎麼來的
Interaction 25 minute(s)

Learning Objectives

  • 說明字元集(每個字元的編號)與編碼(編號怎麼存成位元組)的差別
  • 認識 ASCII、Big5 與 Unicode,看懂 U+XXXX 碼位的寫法
  • 依照 UTF-8 的規則,判斷一個字元要用幾個位元組、每個位元組的前綴代表什麼
  • 解釋亂碼的成因:寫入與讀取用了不同的編碼,並知道怎麼修正

亂碼從哪裡來

你可能遇過這些畫面:同學傳來的 CSV 檔打開變成 中文、舊網頁的中文變成「嚙踝蕭」、字幕檔的中文全部變成問號或「�」。

檔案本身通常沒有壞。電腦裡沒有「字」,只有位元組;每個字元都要先換成數字,再把數字存成位元組。 存的時候用哪一套規則、讀的時候又用哪一套規則,只要兩邊不一樣,就會出現亂碼。

這一課要回答:

  1. 字元怎麼變成數字?ASCII、Big5、Unicode 各是什麼?
  2. Unicode 的數字怎麼存成位元組?UTF-8 的規則是什麼?
  3. 亂碼是怎麼產生的?要怎麼修好?

先分清楚兩個名詞:

名詞 意思 例子
字元集(character set) 有哪些字元,每個字元的編號是多少 Unicode 規定「中」的編號是 U+4E2D
編碼(encoding) 編號怎麼存成一串位元組 UTF-8 把 U+4E2D 存成 E4 B8 AD 三個位元組

ASCII、Big5 這類早期標準把兩件事合在一起定義;Unicode 則把它們分開。

ASCII 與各地的擴充

ASCII(American Standard Code for Information Interchange)第一版在 1963 年發布,用 7 個位元表示 128 個字元(小寫字母在 1967 年的修訂版才加入):

編號(十進位) 十六進位 內容
0~31、127 00~1F、7F 控制字元,例如換行(10)、Tab(9)
32 20 空白
48~57 30~39 數字 0~9
65~90 41~5A 大寫 A~Z
97~122 61~7A 小寫 a~z

大寫與小寫剛好差 32,也就是只差一個位元(A = 0100 0001、a = 0110 0001)。

一個位元組有 8 個位元,ASCII 只用了一半。128~255 這一半,各地拿來放自己的字:西歐的 Windows-1252 放了 é、ñ、ü 這類字母;中文字有上萬個,一個位元組放不下,就用兩個位元組表示一個字。

台灣常用的是 Big5:1984 年由資訊工業策進會(資策會)與五家電腦廠商共同制定,收錄常用字與次常用字共一萬三千多個漢字,例如「中」是 A4 A4。中國用 GB2312、日本用 Shift_JIS,各自規定不同。

問題就出在這裡:同一組位元組,在不同的編碼裡代表不同的字。檔案裡沒有寫明自己用哪一種編碼,讀的程式只能猜,猜錯就是亂碼。

Unicode:全世界的字元一個編號

Unicode 想解決的是「各地各自為政」:給世界上每一種文字的每一個字元一個唯一的編號,稱為碼位(code point),寫成 U+ 加上十六進位數字。第一個版本在 1991 年發布,之後持續加入新的文字與表情符號。

字元 碼位
A U+0041(和 ASCII 的 65 一樣)
é U+00E9
中 U+4E2D
😀 U+1F600

碼位的範圍是 U+0000 到 U+10FFFF,大約一百一十萬個位置。

Unicode 只規定編號;編號要怎麼存成位元組,另外有幾種編碼方式:

編碼 每個字元的長度 常見用途
UTF-8 1~4 個位元組 網頁、檔案、大多數程式語言的原始碼
UTF-16 2 或 4 個位元組 Windows 內部、Java 與 JavaScript 的字串
UTF-32 固定 4 個位元組 程式內部處理,較少用來存檔

UTF-8 的規則

UTF-8 依碼位大小決定用幾個位元組,第一個位元組的開頭(前綴)告訴你這個字元一共有幾個位元組:

碼位範圍 位元組數 位元樣式(x 填入碼位的位元) 例子
U+0000~U+007F 1 0xxxxxxx A → 41
U+0080~U+07FF 2 110xxxxx 10xxxxxx é → C3 A9
U+0800~U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 中 → E4 B8 AD
U+10000~U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 😀 → F0 9F 98 80

這套設計有幾個好處:

  • 和 ASCII 相容:英文字母、數字只用 1 個位元組,而且和 ASCII 完全相同,舊的英文檔案本來就是合法的 UTF-8。
  • 不容易錯位:開頭是 10 的位元組一定是「接續位元組」,從任何地方開始讀,都能找到下一個字元的開頭。
  • 代價是常用中文字要 3 個位元組,比 Big5 的 2 個多。

UTF-8 是 Ken Thompson 與 Rob Pike 在 1992 年設計的,最早用在貝爾實驗室的 Plan 9 作業系統。

手動把「中」編成 UTF-8

「中」的碼位是 U+4E2D,在 U+0800~U+FFFF 之間,要用 3 個位元組:1110xxxx 10xxxxxx 10xxxxxx。程式用位元運算把碼位的 16 個位元切成 4、6、6 三段,分別填進三個位元組。>> 是往右移(丟掉右邊的位元),& 是只留下某幾個位元,| 是把兩組位元合在一起。最後和 Python 內建的 encode() 比較。

互動練習:逐字看 UTF-8

在輸入框打字(或按「插入範例」),表格會逐字列出 Unicode 碼位、UTF-8 的十六進位與二進位(紅色是前綴位元)、UTF-16 與 Big5 的位元組。依任務分別找出 UTF-8 用 3 個、2 個、4 個位元組的字元,最後回答前綴 10 代表什麼。順便比較同一個中文字在 UTF-8 與 Big5 裡各佔幾個位元組,以及表情符號在 Big5 欄位顯示什麼。

想一想:碼位與位元組

1.

A 的 ASCII 編號是 65,C 的編號是多少?

2.

一個 UTF-8 字元的第一個位元組是 11110xxx,這個字元一共用了幾個位元組?

3.

下面的程式會印出什麼?

print(len('台灣'), len('台灣'.encode('utf-8')))
第 1 次作答

亂碼的成因與修正

亂碼 =「寫入時用的編碼」≠「讀取時用的編碼」。幾種常見的樣子:

看到的 通常的原因
中文、café UTF-8 的檔案被當成西歐編碼(Windows-1252)讀:一個字的 2~3 個位元組被拆成好幾個西歐字母
不相干的中文字,夾雜「�」 UTF-8 的檔案被當成 Big5 讀,或反過來
「嚙踝蕭」 文字先被換成替代字元「�」(UTF-8 是 EF BF BD),這些位元組再被當成 Big5 讀
? 存檔時用的編碼根本沒有這個字(例如用 Big5 存表情符號),存檔當下就換成問號,資料已經遺失

前三種只是讀錯,位元組都還在,換成正確的編碼重新開啟就好;最後一種是存錯,資料已經不見了。

避免亂碼的作法:

  • 存檔一律用 UTF-8;程式讀寫檔案時明確指定,例如 Python 的 open('data.csv', encoding='utf-8')。
  • 網頁在 <head> 寫 <meta charset="utf-8">,告訴瀏覽器用哪一種編碼。
  • 文字編輯器多半有「以其他編碼重新開啟」的功能,可以用來救回讀錯的檔案。
  • 有些試算表軟體開啟 UTF-8 的 CSV 檔時會猜錯編碼,可以改用「匯入」功能並指定 UTF-8。

互動練習:製造亂碼再修好它

選擇「存檔時用」與「開檔時用」的編碼,中間會顯示存下來的位元組,右邊是用另一種編碼讀出來的結果(瀏覽器的 TextDecoder 真的解碼)。依任務先製造三種亂碼:UTF-8 存、Big5 讀;Big5 存、UTF-8 讀;UTF-8 存、Windows-1252 讀(記得插入「café」)。回答為什麼英文字母都沒變之後,讓兩邊用同一種編碼,把中文修好。

在 Python 裡編碼與解碼

Python 的字串(str)存的是字元;encode() 把字串換成位元組(bytes),decode() 把位元組換回字串。注意 len() 算字串與算位元組的結果不同,以及最後用錯的編碼 cp1252(Windows-1252)解碼時,印出了熟悉的亂碼。

想一想:亂碼怎麼辦

1.

同學用 UTF-8 存的 CSV 檔,你的軟體用 Big5 開啟,中文全部變成亂碼。這個檔案怎麼了?

2.

下面的程式會印出什麼?

print(len('😀'), len('😀'.encode('utf-8')))
3.

一份文件裡同時有中文、日文假名與表情符號,存檔時應該選哪一種編碼才不會遺失任何字?

第 1 次作答

重點整理

  • 字元集規定每個字元的編號,編碼規定編號怎麼存成位元組;Unicode 是字元集,UTF-8、UTF-16 是它的編碼方式。
  • ASCII 用 7 個位元表示 128 個英文字元;Big5 用 2 個位元組表示一個中文字;各地標準互不相容。
  • Unicode 碼位寫成 U+XXXX,範圍是 U+0000~U+10FFFF;「中」是 U+4E2D。
  • UTF-8 用 1~4 個位元組,第一個位元組的前綴(0、110、1110、11110)說明長度,接續位元組以 10 開頭;英文和 ASCII 相同,常用中文字 3 個位元組,表情符號 4 個。
  • 亂碼是寫入與讀取用了不同的編碼;位元組還在就能用正確的編碼救回,存檔時就變成「?」的字已經遺失。存檔與讀檔一律明確使用 UTF-8。

下一課:文字之外,照片和音樂又是怎麼變成位元的?為什麼一張照片未壓縮要好幾 MB,壓縮後卻小很多?

這一課完成了!
Next lesson:影像與聲音的數位化與壓縮
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。