0%

編碼歷史

1. 名詞定義

1.1. 位元 - bit

位元(英語:Bit,亦稱二進制位)指二進位中的一位,是資訊的最小單位[1]BitBinary digit(二進位數位)的混成詞,由數學家John Wilder Tukey提出可能是1946年提出,但有資料稱1943年就提出了)。這個術語第一次被正式使用,是在香農著名的論文《通信的數學理論》(A Mathematical Theory of Communication)第1頁中。

1.2. 位元組(字節) - bytes

位元組(英語:Byte),通常用作電腦手機手錶等 資訊計量單位,不分資料型態。[1][2] 是通信和資料儲存的概念。一個位元組代表八個位元。從歷史的觀點上,「位元組」表示用於編碼單個字元所需要的位元數量。歷史上位元組長度曾基於硬體為1-48位元不等,最初通常使用6位元或9位元為一位元組。今日標準以8位元作為一位元組,因8為二進位整數。

bit與byte示意圖:

bit & byte

1.3. 字元(字符) - character

字元(character)是人類語文的最基本單位,例如:中文字、英文字母、阿拉伯數字、標點符號等。而字元集(character set)則是指某種語文的全部字元(例如:英文的字元)或部份字元(例如:中文的字元)所形成的集合

  • 圖形字元(graphic characters)
    • 指的是可以被顯示在螢光幕上或是被列印在報表紙上,用以構成文數字資訊或電腦語言的字元。就英文字元集而言,所包含的圖形字元為52個大小寫英文字母、10個阿拉伯數字和一些標點符號,總數還不到100個。但就中文字元集而言,所包含的圖形字元至少是成千上萬的中文字。
  • 控制字元(control characters)
    • 則代表特定的處理功能,可驅使電腦或通信設備執行特定程式,以進行特定處理或表現特定動作,例如:驅使螢光幕上的游標回到行首並換行、驅使印表機換行或跳頁、開始或終止資料的傳輸等。原則上,控制字元不具備可顯示或可列印的圖形。

1.4. 編碼

計算機中儲存的信息都是用二進制數表示的;而我們在屏幕上看到的英文、漢字等字符是二進制數轉換之後的結果。通俗的說,按照何種規則將字符存儲在計算機中,如a用什麼表示,稱為”編碼”;反之,將存儲在計算機中的二進制數解析顯示出來,稱為”解碼”,如同密碼學中的加密和解密。在解碼過程中,如果使用了錯誤的解碼規則,則導致a解析成b或者亂碼。

1.4.1. 字元集(字符集) - characterset

是一個系統支持的所有抽象字符的集合。字符是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。

一組由不同字元構成的有限集合,它對某些特定用途而言,被認為是完整而有意義的。在字元集中,每個字元均有不同的代碼,且所包括的字元數目是固定的。這些組合在一起的字元,有其唯一的表示形式及順序。例如ISO第646號標準「資訊互換用七數元碼字符集」、128個ASCII字元、CNS 11643「中文標準交換碼」等,都是一組有效字元使用於特定原因的字元集。

字符集:多個字符的集合。

字符集的目的是完成映射, 映射完成後二進制數就變成了新的字符

1.4.2. 字元編碼(字符編碼) - Character Encoding

是一套法則,使用該法則能夠對自然語言的字符的一個集合(如字母表或音節表),與其他東西的一個集合(如號碼或電脈衝)進行配對。即在符號集合與數字系統之間建立對應關係,它是信息處理的一項基本技術。通常人們用符號集合(一般情況下就是文字)來表達信息。而以計算機為基礎的信息處理系統則是利用元件(硬件)不同狀態的組合來存儲和處理信息的。元件不同狀態的組合能代表數字系統的數字,因此字符編碼就是將符號轉換為計算機可以接受的數字系統的數,稱為數字代碼。

常見字符集名稱:ASCIIGB2312BIG5GB18030Unicode字符集等。計算機要準確的處理各種字符集文字,需要進行字符編碼,以便計算機能夠識別和存儲各種文字。

其實,在網頁上常見的寫法,我們也會發現UTF-8也常宣告在header中,告訴瀏覽器請以UTF-8編碼格式來閱讀這份文件:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

</body>
</html>

1.5. 位元組順序記號 (BOM)

BIG-5、GBK、UTF-8 等編碼方式都是以單位元組為單位,所以沒有位元組順序的問題,不過 UTF-16 編碼是以兩個位元組為單位,所以需要知道資料使用的位元組順序才能解析資料。

Unocode 推薦判斷位元組順序的方式是 BOM (Byte Order Mark),其原理是在位元組流的開頭使用 FEFF 字元來識別位元組順序,如果在文件開頭讀到 FF FE 就代表使用小端序,反之代表使用大端序。

有人一定會問,FF FE 有沒有可能是沒使用 BOM 的大端序中的 FFFE 這個字元,這個問題其實在設計 BOM 時已經有考慮進去了,在 Unocode 中確保 FFFE 不會被指定為字元,因此也不該出現在資料中,所以 FF FE 只能被解釋為小端序的 FEFF,不可能是大端序的 FFFE

1.6. 代碼頁 (Code Page)

代碼頁的用途是用來區分不同的字元集編碼,告訴系統該使用哪種方式來解析文字編碼,例如記事本讀到 B1 F0,那麼到底該解釋為 GBK 的 “别” 字,還是 BIG5 的 “梗” 字,這兩個字的編碼都是 B1F0,在沒有指定編碼訊息的情況下,Windows 會使用預設的代碼頁來解析文字編碼,Windows 的代碼頁也被稱為 ANSI 代碼頁,在 Windows 上預設代碼頁不能直接修改,必須透過選擇系統的 Locale 間接改變代碼頁。

代碼頁也用於不同編碼之間的轉換,在 Windows 2000 之後,Windows 統一採用 UTF-16 作為系統內部的字元編碼,代碼頁其實就是一張各國文字編碼和 Unocode 的對應表,Windows 就是透過此表實現不同編碼間的轉換。

不知道大家有沒有試過在不同編碼的文件中使用複製貼上,大家一定都有做過,只是可能不會特別去注意文件的編碼是否相同,例如在 GBK 文件中將 “文” 複製貼上到 BIG5 文件中,如果系統只是複製字元的編碼 CEC4 的話,那麼貼過去後看到的應該是 “恅”,可是實際上看到的還是 “文”,所以很明顯系統在這之間做了轉碼的動作,先將 GBK 對應到 Unicode,再將 Unicode 對應到 BIG5,所以字才沒有跑掉,大家無聊可以去試試看。

大家可以再試試上面的 “别”,會發現貼過去怎麼變成了問號 ?,為什麼沒有轉為繁體字 “別” 呢,因為在 Unicode 簡體字别和繁體字別其實是不同的兩個字,對應不同的碼位,所以無法直接轉換。那為什麼會變成問號呢,因為 BIG5 編碼中並沒有簡體字 “别”,系統在轉換的過程中,會將代碼頁對應不到的字用問號 ? 取代,所以才會看到問號,而這個過程是不可逆的,轉換失敗後字碼就被改變了,無法再轉回原來的字。

1.7. CMAP 表

CMAP 表的功能是將 字元編碼字元點陣圖 對應起來,透過此表字型檔才能將文字由字元編碼轉換為字型圖檔顯示在螢幕上,不過一張 CMAP 表只能對應一種編碼方式,如果需要字型檔支援不同的編碼方案就需要多張對應表,因此 CMAP 表可以包含多個子表,每個子表對應一種編碼方案,字型檔可透過多種支援的編碼方案來查表,可以是 Unicode 或其他 Code Page,不過這裡我還沒有更深入研究,目前還只會用 Unicode 來查表,未來有機會再嘗試更多的編碼方案。

img

2. 編碼歷史概圖

encode.drawio

3. ASCII

ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)是基於拉丁字母的一套電腦編碼系統。它主要用於顯示現代英語,而其擴展版本EASCII則可以勉強顯示其他西歐語言。它是現今最通用的單字節編碼系統,並等同於國際標準ISO。

3.1. 標準ASCII

8位二進制中第一位(高位)一直為0 後面7位一共有2^7次, 128種不同的組合, 表示所有的大寫和小寫字母,數字0 到9、標點符號, 以及在美式英語中使用的特殊控制字符。

img

  • 可顯示字元
    • ASCII控制字元的編號範圍是0-31和127(0x00-0x1F和0x7F),共33個字元。
  • 控制字元
    • 可顯示字元編號範圍是32-126(0x20-0x7E),共95個字元。

注意:在電腦的存儲單元中,一個ASCII碼值占一個位元組(8個二進位位元),其最高位元(b7)用作奇偶校驗位。

所謂奇偶校驗,是指在代碼傳送過程中用來檢驗是否出現錯誤的一種方法,一般分奇校驗和偶校驗兩種。

奇校驗規定:正確的代碼一個位元組中1的個數必須是奇數,若非奇數,則在最高位b7添1;偶校驗規定:正確的代碼一個位元組中1的個數必須是偶數,若非偶數,則在最高位b7添1。

參考連結:wiki ASCII

3.2. 擴展ASCII - EASCII(Extended ASCII)

EASCIIExtended ASCII,延伸美國標準資訊交換碼)是將ASCII碼由7擴充為8位元而成。EASCII的內碼是由0到255共有256個字元組成。EASCII碼比ASCII碼擴充出來的符號包括表格符號、計算符號、希臘字母和特殊的拉丁符號。

標準ASCII碼在美國使用基本沒問題,但是一旦推廣到其它國家, 他們有屬於自己的字母和符號, 現有的符號無法滿足需求, 因此其它國家就將8位二進制的第一位置位1, 然後用擴展出來的128種組合, 作為自己國家特殊符號的表示.

比如,法語中的é的編碼為130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。

但是,這裡又出現了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0–127表示的符號是一樣的,不一樣的只是128–255的這一段。

至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,肯定是不夠的,就必須使用多個字節表達一個符號。比如,簡體中文常見的編碼方式是 GB2312,使用兩個字節表示一個漢字,所以理論上最多可以表示 256 x 256 = 65536 個符號。

參考連結:wiki EASCII

參考連結:字符编码笔记:ASCII,Unicode 和 UTF-8

4. GB家族

中國設計了GB系列編碼(“GB”為“國標”的漢語拼音首字母縮寫,即“國家標準”之意)。

4.1. GB2312

計算機發展的很迅速,很快傳入了亞洲,傳入了中國。中國的文字博大精深,光常用的漢字就多達幾千個,ASCII和ISO-8859顯然都是不適用的。所以聰明的中國人想到了一個辦法:用2個字節表示一個漢字。我們知道ASCII但是單字節的,1個字節可以表示256個字符,那兩個字節就可以表示256*256= 65536個漢字,這就基本可以滿足我們了,這就是最早的GB2312編碼。

GB2312中收錄了大概有7000個漢字,同時收錄了拉丁字母、希臘字母、日文平假名及片假名字母、俄語西里爾字母在內的 682 個字符。

4.2. GBK

但是隨著的問題有出現了。在GB2312使用的過程中發現一些生僻字無法處理,所以就出現了GBK。GBK設計兼容GB2312的,收錄了2萬多的漢字和圖形符號。

4.3. GB18030

為了進一步擴展編碼所能表示更多的漢字。信息產業部和國家質量技術監督局在2000年發布了GB 18030。GB 18030總共收錄了7萬多漢字,並且兼容GBK和GB2312。到這裡中國基本所有的漢字就可以在計算機中表示了。

5. BIG5

大五碼(英語:Big5,又稱為五大碼)是使用繁體中文(正體中文)社群中最常用的電腦漢字字元集標準,共收錄13,060個漢字[1]

中文碼分為內碼交換碼兩類,Big5屬中文內碼,知名的中文交換碼有CCCIICNS11643

Big5雖普及於台灣香港澳門等繁體中文區域,但長期以來並非當地的國家/地區標準或官方標準,而只是業界標準倚天中文系統Windows繁體中文版等主要作業系統的字元集都是以Big5為基準,但廠商又各自增加不同的造字與造字區,衍生成多種不同版本。

五大中文套裝軟體,即Big-5軟體,是1984年由中華民國財團法人資訊工業策進會台灣國內13家廠商合作進行「五大軟體專案」,所開發出來的五種中文套裝軟體,分別為「文書處理」、「資料庫」、「試算表」、「通訊」及「繪圖」。

Big5碼的產生,是因為當時個人電腦沒有共通的內碼,導致廠商推出的中文應用軟體無法推廣,並且與IBM 5550王安碼等內碼,彼此不能相容;另一方面,台灣當時尚未推出中文編碼標準。在這樣的時空背景下,為了使台灣早日進入資訊時代,所採行的一個計畫;同時,這個計畫對於以台灣為核心的亞洲繁體漢字圈也產生了久遠的影響。

6. ANSI

ANSI指American National Standards Institute(美國國家標準學會)。

在全世界所有國家和民族的文字符號統一編碼的Unicode編碼方案問世之前,各個國家、民族為了用計算機記錄並顯示自己的字符,都在ASCII編碼方案的基礎上,設計了各自的編碼方案。

比如歐洲先後設計了EASCII和ISO/IEC 8859系列字符編碼方案;為了顯示中文及相關字符,中國設計了GB系列編碼(“GB”為“國標”的漢語拼音首字母縮寫,即“國家標準”之意)。

同樣,日文、韓文、世界各國文字都有它們各自的編碼。所有這些各個國家和地區所獨立制定的既兼容ASCII又互相不兼容的字符編碼,微軟統稱為ANSI編碼。

ANSI編碼不是一種具體的編碼方式,而是一種指定在某些環境下使用某些編碼方式的標準。比如,在中文環境中ANSI的編碼標準為GBK,在日語環境中ANSI的編碼標準則是Shift_JIS編碼。

不同的國家和地區製定了不同的標準,由此產生了 GB2312、GBK、Big5、Shift_JIS 等各自的編碼標準。這些使用 1 至 4 個字節來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文Windows操作系統中,ANSI 編碼代表 GBK 編碼;在日文Windows操作系統中,ANSI 編碼代表 Shift_JIS 編碼。 “ANSI編碼”被稱為“本地編碼”。

ANSI另外一個名稱是MBCS,MBCS(Multi-Byte Character Set)是多字節編碼的統稱。目前為止大家都是用了雙字節,所以有時候也叫做DBCS(Double-Byte Character Set)。必須明確的是,MBCS並不是某一種特定的編碼,Windows裡根據你設定的區域不同,MBCS指代不同的編碼,而Linux裡無法使用MBCS作為編碼。在Windows中你看不到MBCS這幾個字符,因為微軟為了更加洋氣,使用了ANSI來嚇唬人,記事本的另存為對話框裡編碼ANSI就是MBCS。

6.1. 實驗ANSI編碼

用Notepad++創建一個文本文件text.txt,其默認編碼格式為ANSI(乍看之下,還以為是ASCII呢),輸入漢字居然不是亂碼:
img

保存為test.txt,發送給你美國的同事Bob。他也用Notepad++,不幸的是,卻發現你的文件內容是這樣的:
img

也許你會認為:你用的是中文系統,能正常顯示中文;他用的是英文系統,不能顯示中文!

這麼想,好像很有道理呢!

但是再細想一下:一個系統顯示亂碼,說明它不支持這種編碼格式(或者解碼方式不對)。難道英文系統不支持ANSI?難道ANSI是一種中文編碼?

如果你身邊有一個韓文系統,也裝一個Notepad++,默認還是ANSI編碼,你可以輸入“한국어”,發現也能正常顯示:
img
但是你要輸入“漢字”可能就會發現是亂碼了…

通過這個反例,就可以說明ANSI不是一種中文編碼。那麼,ANSI到底是什麼編碼?

用十六進制編輯器打開內容為“漢字”的test.txt文件,你會發現其中baba和d7d6正好是“漢”和“字”兩個字的GBK編碼值。
img

同樣,用十六進制編輯器打開內容為“한국어”的test.txt文件,你會發現其中c7d1、b1b9和beee正好是“한”、“국”和“어”三個字符的EUC-KR編碼值。
img

由此可以看出:其實ANSI並不是某一種特定的字符編碼,而是在不同的系統中,ANSI表示不同的編碼。你的美國同事Bob的系統中ANSI編碼其實是ASCII編碼(ASCII編碼不能表示漢字,所以漢字為亂碼),而你的系統中(“漢字”正常顯示)ANSI編碼其實是GBK編碼,而韓文系統中(“한국어”正常顯示)ANSI編碼其實是EUC-KR編碼。

6.2. Windows系統是如何區分ANSI背後的真實編碼的呢?

微軟用一個叫“ Windows code pages ”(在命令行下執行chcp命令可以查看當前code page的值)的值來判斷系統默認編碼,比如:簡體中文的code page值為936(它表示GBK編碼,win95之前表示GB2312,詳見:Microsoft Windows’ Code Page 936),繁體中文的code page值為950(表示Big-5編碼)。

6.3. 通過修改Windows code pages的值來改變“ANSI編碼”

命令提示符下,我們可以通過chcp命令來修改當前終端的active code page,例如:
(1) 執行:chcp 437,code page改為437,當前終端的默認編碼就為ASCII編碼了(漢字就成亂碼了);
(2) 執行:chcp 936,code page改為936,當前終端的默認編碼就為GBK編碼了(漢字又能正常顯示了)。
上面的操作只在當前終端起作用,並不會影響系統默認的“ANSI編碼”。(更改命令行默認codepage參看:設置cmd的codepage的方法)。

Windows下code page是根據當前系統區域(locale)來設置的,要想修改系統默認的“ANSI編碼”,我們可以通過修改系統區域來實現(“控制面板” =>“時鐘、語言和區域”=>“區域和語言”=>“管理”=>“更改系統區域設置…”):
img
圖中的系統locale為簡體中文,意味著當前“ANSI編碼”實際是GBK編碼。當你把它改成Korean(Korea)時,“ANSI編碼”實際是EUC-KR編碼,“한국어”就能正常顯示了;當你把它改成English(US)時,“ANSI編碼”實際是ASCII編碼,“漢字”和“한국어”都成亂碼了。(改了之後需要重啟系統的。。。)

說明:locale是國際化與本地化中重要的概念,本文不深入講解該內容。

常見編碼:

codepage=936 簡體中文GBK
codepage=950 繁體中文BIG5
codepage=437 美國/加拿大英語
codepage=932 日文
codepage=949 韓文
codepage=866 俄文

參考連結:ANSI是什麼編碼?

7. UNICODE

由於 ASCII 只能儲存最多 256 種組合,顯然對於多國語系來說相當不夠,因此出現 Unicode

Unicode字碼表(Unicode characters)收集了大量的Unicode字符代碼,以表格形式呈現。Unicode(統一碼、萬國碼、單一碼)是電腦科學領域裡的一項業界標準,包括字符集(字元集)、編碼方案等。Unicode 是為了解決傳統的字元編碼方案的局限而產生的,它為每種語言中的每個字元設定了統一,併且唯一的二進制編碼,以滿足跨語言、跨平台進行文本轉換、處理的要求。1990年開始研發,1994年正式公佈。

Unicode 就像一個資料庫,每一個文字符號(symbol)都可以對應到一組數字

在 Unicode 中常用 U+<hex> 表示,U+ 後面的 16 進制數就叫「碼點(Code Point)」或「編碼位置」。這個編碼位置是唯一的。透過這種方式可以簡單的存取特定文字符號而不用直接輸入符號本身,例如,U+0000 則碼點為 0000

7.1. 基本平面和輔助平面(BMP and SMP)

Unicode 編碼範圍從 U+0000U+10FFFF 超過 110 萬個符號。

基本平面(BMP, Basic Multilingual Plane):由兩個位元組組成,是 Unicode 中最前面的 65536 個文字符號,寫成 16 進制就是從U+0000U+FFFF。所有最常見的字符都放在這個平面,這是 Unicode 最先定義和公佈的一個平面。

輔助平面(SMP, Supplementary planes or Astral planes):由三個位元組組成,除了基本平面外,剩下的文字符號都放在輔助平面,碼點範圍從 U+010000 一直到 U+10FFFF,如果某個字符需要超過 4 位數的 16 進制來表示那它就屬於輔助平面。

目前的Unicode字元分為17組編排,每組稱為平面(Plane),而每平面擁有65536(即216)個代碼點。然而目前只用了少數平面。

3330817-c188b53396c14318

平面 始末字元值 中文名稱 英文名稱
0號平面 U+0000 - U+FFFF 基本多文種平面 Basic Multilingual Plane,簡稱BMP
1號平面 U+10000 - U+1FFFF 多文種補充平面 Supplementary Multilingual Plane,簡稱SMP
2號平面 U+20000 - U+2FFFF 表意文字補充平面 Supplementary Ideographic Plane,簡稱SIP
3號平面 U+30000 - U+3FFFF 表意文字第三平面 Tertiary Ideographic Plane,簡稱TIP
4號平面 至 13號平面 U+40000 - U+DFFFF (尚未使用)
14號平面 U+E0000 - U+EFFFF 特別用途補充平面 Supplementary Special-purpose Plane,簡稱SSP
15號平面 U+F0000 - U+FFFFF 保留作為私人使用區(A區)[1] Private Use Area-A,簡稱PUA-A
16號平面 U+100000 - U+10FFFF 保留作為私人使用區(B區)[1] Private Use Area-B,簡稱PUA-B

參考連結:Unicode字元平面對映

7.2. Unicode和Unicode big endian有什麼區別?

7.2.1. 位元組順序

學過組語的大大們應該很了解,不同 CPU 在處理多位元資料型態時,暫存器和記憶體存放資料的方式有兩種大端序 (big-endian) 和小端序 (little-endian)。

  • 大端序會將資料的高位位元組儲存在記憶體的低位地址。

  • 小端序會將資料的低位位元組儲存在記憶體的低位地址。

例如 “字” 的 Unicode 是 5B57,5B 為高位位元組,57 為低為位元位,如果開啟記事本後讀取到的順序是 5B 57,就是使用大端序儲存,反之就是小端序。

像我第一次在 C# 將 string 轉成 hex 時就覺得很困惑,為什麼 Unicode 是 5B57,而我看到的卻是 575B,後來才知道是因為 C# 和 C++ 一樣,位元組順序會隨著 CPU 架構而不同,在 Intel x86 x64 架構下會採用小端序,所以才會看到低位地址57放在前面,而 JAVA 因為 JVM 的原因沒有這種差異,都是採用大端序。

主機間不同的位元組順序,會造成網路通訊的困難,因此 TCP/IP 協議也規範了統一的位元組順序 網絡位元組序,採用大端序,大部分的網路協定也都是使用大端序。

不同的計算機系統會以不同的順序保存字節。這意味著字符U+4E2D在UTF-16編碼方式下可能被保存為4E 2D或者2D 4E,這取決於該系統使用的是大尾端(big-endian)還是小尾端(little-endian)。 (對於UTF-32編碼方式,則有更多種可能的字節排列。)只要文檔沒有離開你的計算機,它還是安全的——同一台電腦上的不同程序使用相同的字節順序(byte order)。但是當我們需要在系統之間傳輸這個文檔的時候,也許在萬維網中,我們就需要一種方法來指示當前我們的字節是怎樣存儲的。不然的話,接收文檔的計算機就無法知道這兩個字節4E 2D表達的到底是U+4E2D還是U+2D4E。

img

7.2.2. 大端序(英:big-endian)

  • 數據以8bit為單位:地址增長方向 → 0x0A 0x0B 0x0C 0x0D
  • 示例中,最高位字節是0x0A 存儲在最低的內存地址處。下一個字節0x0B存在後面的地址處。正類似於十六進製字節從左到右的閱讀順序。
  • 數據以16bit為單位: 地址增長方向 → 0x0A0B 0x0C0D
  • 最高的16bit單元0x0A0B存儲在低位。

7.2.3. 小端序(英:little-endian)

  • 數據以8bit為單位: 地址增長方向 → 0x0D 0x0C 0x0B 0x0A
  • 最低位字節是0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。
  • 數據以16bit為單位: 地址增長方向 → 0x0C0D 0x0A0B
  • 最低的16bit單元0x0D0C存儲在低位。

以漢字為例,Unicode碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。

存儲的時候,4E在前,25在後,就是Big endian方式(也就是4E25);25在前,4E在後,就是Little endian方式(也就是254E)。

Unicode規範中定義,每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做”零寬度非換行空格”(Zero Width No-Break Space),用FEFF表示。這正好是兩個字節,而且FF比FE大1。

如果一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;如果頭兩個字節是FF FE,就表示該文件採用小頭方式。

  • Unicode:編碼是四個字節”FF FE 25 4E”,其中”FF FE”表明是小頭方式存儲,真正的編碼是4E25。
  • Unicode big endian:編碼是四個字節”FE FF 4E 25″,其中”FE FF”表明是大頭方式存儲。

7.3. Unicode 版本

版本 發布日期 書籍 對應ISO/IEC 10646版本 文字數 字元數
總計[註 1] 已知的擴增
1.0.0 1991年10月 ISBN 0-201-56788-1(Vol. 1) 24 7,161 最初包含的文字有:阿拉伯字母亞美尼亞字母孟加拉文注音符號西里爾字母天城文喬治亞字母希臘字母古吉拉特文古木基文諺文希伯來字母平假名卡納達文片假名寮文字拉丁字母馬拉雅拉姆文奧里亞文泰米爾文泰盧固文泰文字藏文[9]
1.0.1 1992年6月 ISBN 0-201-60845-6(Vol. 2) 25 28,359 定義中日韓統一表意文字最初的20,902個字[10]
1.1 1993年6月 ISO/IEC 10646-1:1993 24 34,233 於原有2,350個諺文字母的基礎上新增4,306個諺文字母。移除藏文[11]
2.0 1996年7月 ISBN 0-201-48345-9 ISO/IEC 10646-1:1993與其第5-7修訂版 25 38,950 移除原有的諺文字母設定,於新的編碼範圍更換成11,172個新的諺文字母。藏文重新加入,但編碼位置更換。代理字元機制建立,並將第15與第16平面分配給私人使用區[12]
2.1 1998年5月 ISO/IEC 10646-1:1993與其第5-7修訂版,以及第18修訂版中新增的2個字元 25 38,952 新增歐元符號物件替換字元[13]
3.0 1999年9月 ISBN 0-201-61633-5 ISO/IEC 10646-1:2000 38 49,259 新增切羅基文吉茲字母高棉字母蒙古字母緬文歐甘字母盧恩字母僧伽羅文敘利亞字母它拿字母加拿大原住民音節文字彝文,以及部分盲文圖案。[14]
3.1 2001年3月 ISO/IEC 10646-1:2000ISO/IEC 10646-2:2001 41 94,205 新增德瑟雷特字母(英語:Deseret alphabet)、哥特字母古義大利字母音樂符號拜占庭音樂符號,追加了42711個中日韓統一表意文字CJK-B)。[15]
3.2 2002年3月 ISO/IEC 10646-1:2000與其第1修訂版ISO/IEC 10646-2:2001 45 95,221 新增菲律賓文字布錫文哈努諾文他加祿文塔格巴奴亞文。[16]
4.0 2003年4月 ISBN 0-321-18578-1 ISO/IEC 10646:2003 52 96,447 新增賽普勒斯音節文字林布字母線形文字B奧斯曼亞字母蕭伯納字母德宏傣文烏加里特字母以及六十四卦。[17]
4.1 2005年3月 ISO/IEC 10646:2003與其第1修訂版 59 97,720 新增布吉文格拉哥里字母佉盧文西雙版納傣文古波斯文錫爾赫特文提非納文科普特字母希臘文區段分離。新增了古希臘音樂符號。[18]
5.0 2006年7月 ISBN 0-321-48091-0 ISO/IEC 10646:2003與其第1、2修訂版,以及第3修訂版中新增的4個字元 64 99,089 新增峇里文楔形文字西非書面文字八思巴文腓尼基字母。[19]
5.1 2008年4月 ISO/IEC 10646:2003與其第1-4修訂版 75 100,713 新增卡利亞文占婆字母克耶黎文絨巴文利西亞文呂底亞文桑塔利文拉讓文索拉什特拉文巽他文瓦伊文。同時增加了斐斯托斯圓盤麻將多米諾骨牌符號。對緬甸文做了重要的補充,追加了手抄縮寫的額外字母,追加了大寫ẞ。[20]
5.2 2009年10月 ISBN 978-1-936213-00-9 ISO/IEC 10646:2003與其第1-6修訂版 90 107,361 新增阿維斯陀文巴姆穆文字埃及象形文字加汀納符號表,涵蓋1071個符號)、亞拉姆文巴拉維碑銘體帕提亞碑銘體爪哇文凱提文老傈僳文曼尼普爾文南阿拉伯字母古突厥文撒瑪利亞字母老傣文傣越文。追加4,149個中日韓統一表意文字CJK-C),同時擴充了古韓文吠陀梵文的字元。[21]
6.0 2010年10月 ISBN 978-1-936213-01-6 ISO/IEC 10646:2010與印度盧比符號 93 109,449 新增巴塔克字母婆羅米文字曼達字母紙牌符號、交通標誌地圖符號、鍊金術符號顏文字繪文字。追加222個額外的中日韓統一表意文字CJK-D)。[22]
6.1 2012年1月 ISBN 978-1-936213-02-3 ISO/IEC 10646:2012 100 110,181 新增查克馬字母麥羅埃文麥羅埃象形文字柏格理苗文夏拉達文索拉僧平文字泰克里文。[23]
6.2 2012年9月 ISBN 978-1-936213-07-8 ISO/IEC 10646:2012與土耳其里拉符號 100 110,182 土耳其里拉符號[24]
6.3 2013年9月 ISBN 978-1-936213-08-5 ISO/IEC 10646:2012與6個字元 100 110,187 5個雙向排版符號。[25]
7.0 2014年6月 ISBN 978-1-936213-09-2 ISO/IEC 10646:2012與其第1、2修訂版,以及俄羅斯盧布符號 123 113,021 新增巴薩字母高加索阿爾巴尼亞字母杜普雷嚴速記愛爾巴桑字母古蘭塔文可吉文庫達瓦迪文線形文字A馬哈佳尼文摩尼教字母門得文字莫迪字母默文納巴泰字母古北阿拉伯文古彼爾姆文楊松錄苗文帕米拉文字袍清豪文詩篇巴列維文悉曇文字底羅仆多文瓦蘭齊地文以及裝飾符號。[26]
8.0 2015年6月 ISBN 978-1-936213-10-8 ISO/IEC 10646:2014與其第1修訂版,以及喬治亞拉里符號、9個中日韓統一表意文字與41個表情符號[27] 129 120,737 增加阿洪姆文安納托利亞象形文字哈坦文穆爾塔尼文古匈牙利字母薩頓手語譜寫、5,771個中日韓統一表意文字字元(CJK-E)、切羅基文小寫字母,以及五種繪文字膚色修改字元。[28]
9.0 2016年6月 ISBN 978-1-936213-13-9 ISO/IEC 10646:2014與其第1、2修訂版,阿德拉姆字母、尼泊爾紐瓦字母、日本電視符號和74個繪文字表情與符號。[29] 135 128,237 新增阿德拉姆字母比奇舒奇文象雄文尼泊爾紐瓦字母歐塞奇字母西夏文以及74個繪文字[30]
10.0 2017年6月 ISBN 978-1-936213-16-0 ISO/IEC 10646:2017,新增56個繪文字符號、385個變體假名字元,和3個札那巴札爾字元[31] 139 136,755 札那巴札爾索永布文字馬薩拉姆貢德文字女書變體假名(非標準平假名)、7,494個中日韓統一表意文字CJK-F)與56個繪文字[32]
11.0 2018年6月 ISBN 978-1-936213-19-1 ISO/IEC 10646:2017與其第1修訂版,新增145個繪文字符號、5個急用漢字,copyleft符號、中國象棋符號等[33] 146 137,374 多格拉文喬治亞文騎士體大寫字母、貢賈拉貢德文哈乃斐羅興亞文字望加錫文梅德法伊德林文老粟特字母粟特字母以及145個繪文字[34]
12.0 2019年3月 ISBN 978-1-936213-22-1 ISO/IEC 10646:2017與其第1、2修訂版,新增61個繪文字符號、一些方言苗文字元、古日文用小型日文假名泰米爾文的符號、聖書體控制字元等[35] 150 137,928 埃利邁文南迪城文創世紀苗文文喬文以及61個繪文字[36]
12.1 2019年5月 ISBN 978-1-936213-25-2 150 137,929 只在U+32FF新增了一個字元,即日本新年號令和的合字。[37]
13.0 2020年3月 ISBN 978-1-936213-26-9 ISO/IEC 10646:2020[38] 154 143,924 花剌子模語迪維西語島字母(英語:Dhives akuru)、契丹小字庫德語字母雅茲迪文、4969個中日韓統一表意文字(4939個位於擴充區GCJK-G))、書寫豪薩語用的阿拉伯附加字母、沃洛夫語、其他非洲語言、在巴基斯坦書寫印德科語(英語:Hindko)和旁遮普語的補充字元、粵語用的注音符號創用CC授權符號、1970年代和1980年代電訊用圖符、55個繪文字[39]
14.0 2021年9月 ISBN 978-1-936213-29-0 159 144,697 托托文,用於在印度東北部書寫托托語塞普勒斯-米諾斯文字,一種未破譯的歷史文字,主要用於賽普勒斯島維斯庫奇文,一種用於書寫阿爾巴尼亞語的歷史文字,正在經歷現代復興回鶻字母,一種歷史上在中亞和其他地方用於書寫突厥語、漢語、蒙古語、藏語和阿拉伯語的文字唐薩文,一種現代文字,用於書寫在印度和緬甸使用的唐薩語位於第一輔助平面的拉丁文擴充區段(擴展F和G)增加了許多擴充IPA字母 ,和許多用於拉丁語及阿拉伯文字的補充字母,這些字母用於非洲和伊朗、巴基斯坦、馬來西亞、印度尼西亞、爪哇和波士尼亞書寫語言,以及書寫敬語,以及為《古蘭經》使用的補充。其它的一些字元添加支援北美和菲律賓、印度和蒙古的語言的文字追加9個額外的中日韓統一表意文字(基本多文種平面、第二輔助平面)。

參考連結:UNICODE 維基百科

8. UTF-8

Unicode 解決的問題是將多國語言中的每一個文字符號都能夠對應到一組數字,但 Unicode 並沒有說明儲存這些數字的方式

UTF-88-bit Unicode Transformation Format)是一種針對Unicode的可變長度字元編碼,也是一種字首碼。它可以用一至四個位元組對Unicode字元集中的所有有效編碼點進行編碼,屬於Unicode標準的一部分,最初由肯·湯普遜羅布·派克提出。[2][3]由於較小值的編碼點一般使用頻率較高,直接使用Unicode編碼效率低下,大量浪費記憶體空間。UTF-8就是為了解決向下相容ASCII碼而設計,Unicode中前128個字元,使用與ASCII碼相同的二進位值的單個位元組進行編碼,而且字面與ASCII碼的字面一一對應,這使得原來處理ASCII字元的軟體無須或只須做少部份修改,即可繼續使用。因此,它逐漸成為電子郵件網頁及其他儲存或傳送文字優先採用的編碼方式。

自2009年以來,UTF-8一直是全球資訊網的最主要的編碼形式(對所有,而不僅是Unicode範圍內的編碼)(並由WHATWG宣布為強制性的「適用於所有事物(for all things)」,[4]截止到2019年11月, 在所有網頁中,UTF-8編碼應用率高達94.3%(其中一些僅是ASCII編碼,因為它是UTF-8的子集),而在排名最高的1000個網頁中占96%。[5] 第二熱門的多位元組編碼方式Shift JIS和GB 2312分別具有0.3%和0.2%的占有率。[6][7][1]Internet郵件聯盟( Internet Mail Consortium, IMC)建議所有電子郵件程式都能夠使用UTF-8展示和建立郵件,[8] W3C建議UTF-8作為XML檔案和HTML檔案的預設編碼方式。[9]網際網路工程工作小組(IETF)要求所有網際網路協定都必須支援UTF-8編碼[10]網際網路郵件聯盟(IMC)建議所有電子郵件軟體都支援UTF-8編碼。[11]

img

8.1. 編碼規則

UTF-8 的編碼規則很簡單,只有二條:

  1. 對於單一位元組的文字符號,位元組的第一位設為 0,後面 7 位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的
  2. 對於 n 位元組的文字符號(n > 1),第一個位元組的前 n 位都設為 1,第 n + 1 位設為 0,後面位元組的前兩位一律設為 10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。

Imgur

8.1.1. 範例一:A

舉例來說,英文字「A」的 Unicode 是 U+0041,也就是 (65)10,亦等同於 (0100 0001)2 因此根據上表顯示介於十六進制 0000 0000 ~ 0000 007F 範圍內,也就是屬於第一組。

所以「A」的 UTF-8 編碼只需要一個位元組,格式就是 0xxxxxxx。將「A」Unicode 的內容直接填入 x,即可得到「A」的 UTF-8 編碼為 0100 0001,轉換成 16 進制就是 (41)16。

8.1.2. 範例二:臺

舉例來說,中文字「臺」的 Unicode 是 U+81FA,也就是 (33274)10,亦等同於 (1000 0001 1111 1010)2,因此根據上表顯示介於十六進制 00000800 ~ 0000FFFF 這個範圍內,也就是屬於第三組。

所以「臺」的 UTF-8 編碼需要三個位元組,格式就是 1110xxxx 10xxxxxx 10xxxxxx。然後從「臺」的最後一個二進制位數開始,依次由後往前添入格式中的 x,多出來的位數補 0

於是便可得到「臺」的 UTF-8 編碼是 11101000 10000111 10111010,轉換成十六進制就是 (E887BA)16。

Imgur

參考連結:Unicode 字串編碼(encode, decode)

9. UTF-8與BOM

BOM – Byte Order Mark,中文名譯作“字節順序標記”。在UCS 編碼中有一個叫做 “Zero Width No-Break Space” ,中文譯名作“零寬無間斷間隔”的字符,它的編碼是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不應該出現在實際傳輸中。 UCS 規范建議我們在傳輸字節流前,先傳輸字符 “Zero Width No-Break Space”。這樣如果接收者收到 FEFF,就表明這個字節流是 Big-Endian 的;如果收到FFFE,就表明這個字節流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零寬無間斷間隔”)又被稱作 BOM。

BOM(byte order mark)是為 UTF-16 和 UTF-32 準備的,UTF-8 不需要 BOM 來表明字節順序,但可以用 BOM 來表明編碼方式。字符 “Zero Width No-Break Space” 的 UTF-8 編碼是 EF BB BF。所以如果接收者收到以 EF BB BF (十六進制)開頭的字節流,就知道這是 UTF-8編碼了。 Windows 就是使用 BOM 來標記文本文件的編碼方式的。微軟在 UTF-8 中使用 BOM 是因為這樣可以把 UTF-8 和 ASCII 等編碼明確區分開,但這樣的文件在 Windows 之外的操作系統裡會帶來問題。它產生的BUG包含但不僅限於:

  • HTML空白行
  • div之間莫明的間隔
  • 亂碼
  • Ssl出錯
  • 編譯錯誤

在Linux中的解決方案,在Linux中可以用vim很方便地檢測、去除BOM頭

檢測是否有BOM頭:

1
:set bomb?

去除BOM頭:

1
2
:set encoding=utf-8
:set nobomb

添加BOM頭也很簡單:

1
2
:set encoding=utf-8
:set bomb

利用python去除文件中BOM的方法:

1
2
3
4
5
import codecs
data = open("Test.txt").read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
print(data.decode("utf-8"))

10. 程序角度看unicode

如果沒有一個統一的編碼可以讓程序來處理,那麼同樣的程序就需要維護不同的版本。 Unicode的出現就是為了解決不同語言間的問題,不同的編碼可以和Unicode進行轉換。

img

在計算機內存中,統一使用Unicode編碼,當需要保存到硬盤或者需要傳輸的時候,就轉換為UTF-8編碼。用記事本編輯的時候,從文件讀取的UTF-8字符被轉換為Unicode字符到內存裡,編輯完成後,保存的時候再把Unicode轉換為UTF-8保存到文件:

unicode.drawio

10.1. Python源代碼文件的執行過程

我們都知道,磁盤上的文件都是以二進制格式存放的,其中文本文件都是以某種特定編碼的字節形式存放的。對於程序源代碼文件的字符編碼是由編輯器指定的,比如我們使用Pycharm來編寫Python程序時會指定工程編碼和文件編碼為UTF-8,那麼Python代碼被保存到磁盤時就會被轉換為UTF-8編碼對應的字節(encode過程)後寫入磁盤。

當執行Python代碼文件中的代碼時,Python解釋器在讀取Python代碼文件中的字節串之後,需要將其轉換為Unicode字符串(decode過程)之後才執行後續操作。

python_exec_encode_decode.drawio

10.2. python 編碼的unicode

Python3 裡的字串可以用兩個型別來表示:(1) str(2) bytes(其實 str 就是一般所謂的 unicode ,以下統稱為 unicode )

在 Python 的程式裡,會建議所有「內部」的商業邏輯操作都使用 unicode 來處理,只有在與「外界」溝通時,才需要轉換成 bytes。

這邊可以定義為「外界」的可以有下列幾個,在 Python3 裡的 function 參數都只吃 bytes 而且,傳 unicode 字串就會 exception

  1. File I/O (read, write)
  2. System I/O (stdin, stdout, stderr)
  3. Binary I/O (http content)

下面這段說明應該滿清楚的

  • Use .encode() to convert human text to bytes.
  • Use .decode() to convert bytes to human text.

img

概念上來說,就是 Python Application 「內部」核心處理的部分都用以 Unicode 處理,「外界」的部分,不管是 Terminal 的 stdout / stdin、HTTP content、File ,只要跟 I/O 有關,都是以 bytes 來處理,進來一律 decode 成 unicode,出去一律 encode 成 bytes。

11. MySQL utf8與utf8mb4的差別

11.1. 網站資料庫utf8編碼用得好好的,為什麼要多一個utf8mb4?

一般情況下,架設網站使用MySQL的utf8就夠用了,但隨著網路資訊的快速進步,需求越來越多元,有些特殊漢字或是常用的emoji(一種特殊的Unicode編碼,在ios跟安卓手機都會看過),這其實都是需要使用四個位元組網頁上才能完整呈現,相對MySQL支援的utf8只有三個位元組,大部分的中文漢字雖然夠用,但是卻無法滿足到所有的字元,若是要使用特殊漢字或是表情符號,就無法使用MySQL的utf8字符集儲存,網站後台操作時會造成錯誤。

11.2. UTF-8原本不就支援四個位元組嗎?為什在MySQL裡只能支援三個位元,還要多此一舉端出一個utf8mb4?

是的,你所知道的UTF-8字符集是支援最多四個位元組沒有錯,但因為MySQL最初開發時,認為三個位元組就夠用了不需要支援到四個位元組,於是把utf8設定為最多支援三個位元組,並不是我們所熟知的UTF-8支援四位元組的字符集,可說是此MySQL utf8非彼UTF-8啊!!

這情況,導致在使用MySQL utf8編碼時使用到生僻字特殊漢字,或是表情符號時,會出錯誤插入失敗(因為只支援到三個位元組),以為是編碼錯誤,找了老半天竟然是系統不支援字符的窘境。

這個漏洞,一直到2010年才得以修復,MySQL端出了一個叫做utf8mb4的字符集來在MySQL內部使用,後面的mb4意思是most bytes 4,表示支援最多四個位元組啦,這樣就可以順利插入生僻字或是Emoji表情符號了。有趣的是官方端出utf8mb4時(這才是真正的UTF-8字符集)就統一將原本的utf8改名為utf8mb3了,也許是容易分辨吧!

11.3. 那我如果想要從utf8轉換到utf8mb4會不會不相容,空間與速度的變化?

utf8與utf8mb4差別是在後者多了一個位元組,但兩者都是相同的儲存特性、相同的編碼,所以網站在升級到utf8mb4時,是不用擔心字元轉換出現亂碼或資料遺失的問題,網頁可以正常顯示。

官方資料也有針對這項疑問作出解釋:

1
For a BMP character, utf8mb4 and utf8mb3 have identical storage characteristics: same code values, same encoding, same length.

也就是強調不用擔心轉換字符的問題,程式編碼等都是相同的。

11.4. utf8mb4_unicode_ci vs utf8mb4_general_ci

xxx_general_ci與xxx_unicode_ci 是一種連線校對的規則,這兩項的差別,也是很多人在操作時不知道如何選擇的地方,在這邊會詳細介紹他的功能和兩者的差別,幫助你選擇。

general版本比unicode來的快速,但是相對的準確率沒有unicode來的精準,會存在一些的錯誤,原因是因為Unicode支持擴展字符集,他的文字涵蓋率可說無論全球哪種文字,只要保存成Unicode編碼都可以正常解釋,但也因為編碼體積比較大,佔網站伺服器空間較多,所以速度會稍微慢一點;general不支持擴展字集,是比較古老的校對規則,準確率沒有這麼高但是速度也相對快速。

這兩項連線校對規則最經典的不同就是發生在以下這個字元「ß」

1
2
若在utf8_general_ci 他會顯示為ß = s
在utf8_unicode_ci 則會顯示為ß = ss

而在MySQL官方網站則將這現象做了一個總結方便使用者做選擇:

1
2
3
utf8_general_ci also is satisfactory for both German and French, except that ß is equal to s, and not to ss. 
If this is acceptable for your application, you should use utf8_general_ci because it is faster.
If this is not acceptable (for example, if you require German dictionary order), use utf8_unicode_ci because it is more accurate.

當架設多國語言網站遇到德文與法文翻譯時,general可能會出現小錯誤,但官方認為若是一般使用可以選擇general版本,因為速度較快,建議若是有字典,需要正確率高的文字需求,則建議使用準確率極高的Unicode版本。

而前述都是舉例原本的utf8做解釋,若套用成utf8mb4_general_ci或是utf8mb_unicode_ci的連線校對也是跟utf8的舉例一樣的,只是就如同前面所說的,生僻字與特殊符號能否正確顯示的差別而已。

11.5. 重點整理

  • MySQL utf8無法顯示生僻字與Emoji表情符號,utf8mb4可以。
  • 原本MySQL內的utf8不是一般的UTF-8,他只有三個位元組,utf8mb4才是四個位元組,是真正的UTF-8字符集。
  • utf8轉utf8mb4不會發生不相容的問題。
  • utf8mb4_unicode_ci 準確但比較慢utf8mb4_general_ci 速度快但相對不準確。

12. 全字庫 中文標準交換碼

如何輸入罕用字

本文將介紹如何輸入罕用字,讓它可以跨各種作業系統及各文件編輯器,放上了網頁也沒問題。

到CNS11643網站 http://www.cns11643.gov.tw/→ 依部首或注音查「字」→ 找出該字的 unicode→ 到 word 輸入 unicode→ 輸入完按「Alt+x」→ 複製該字

如何造字

詳細可以參考國立高雄大學這篇 造字集步驟

13. 參考資源