Unicode 和 UTF-8 是什麼關係?

火丁笔记發表於2015-10-14

絕大多數程式設計師都聽說過 UnicodeUTF-8,但是清楚它們之間關係的人就不多了,關於這個問題,與其蒼白的陳述它們的概念,不如舉例子說明來得自然。

我前些天碰到一個需求:隨機生成幾個漢字。原本我便對編碼之類的問題發怵,所以完全搞不清楚狀況,無奈之下我便上網搜尋了一個 PHP 版本的實現:

<?php
$zh = '';
for($i = 0; $i < 3; $i++) {
    $zh .= '&#'. rand(19968, 40869) . ';';
}
echo mb_convert_encoding($zh, 'UTF-8', 'HTML-ENTITIES');
?>

不過程式碼裡的「19968」和「40869」是什麼玩意?這又牽扯到 Unicode code points,為了更好的說明問題,我們需要把如上十進位制轉換成十六進位制:

shell> php -r 'echo dechex(19968);'
4e00

shell> php -r 'echo dechex(40908);'
9fcc

在 Unicode 官方網站,我們能查到 Unihan Grid Index,其中 CJK Unified Ideographs 部分包含了大部分的漢字,其 code points 恰恰是從 U+4E00 到 U+9FCC!

單單上面一個例子還不足以說明問題,下面我們挑選一個「」字深入說明一下:

Unicode

Unicode

因為我們編碼是 UTF-8,所以就先看看「博」字的 UTF-8 編碼是什麼:

<?php
$string = "博";
for ($i = 0; $i < strlen($string); $i++) {
    echo dechex(ord($string[$i]));
}
?>

程式碼看上去有點冗長,實際上利用 pack/unpack 函式可以很簡單的實現類似的邏輯:

shell> php -r 'var_dump(unpack("H6", "博"));'
array(1) {
  ["codes"]=>
  string(6) "e58d9a"
}

於是乎「博」字的 UTF-8 編碼是「e58d9a」,再看怎麼得到 unicode code point:

shell> php -r 'echo base_convert("e58d9a", 16, 2);'
11100101 10001101 10011010

如上拿到了「博」字的二進位制表示,實際上其 unicode code point 就隱藏在這裡。通常漢字用 UTF-8 表示時是三個位元組,格式為「1110XXXX 10XXXXXX 10XXXXXX」,除掉標誌位,把剩餘對應位置上的資料抽取出來連線在一起,就得到了 Unicode code point,也就是「0101 001101 011010」,剩下的就簡單了,把它從二進位制轉換成十六進位制即可:

shell> php -r 'echo base_convert("0101001101011010", 2, 16);'
535a

需要說明的是,如果你僅僅看「博」字,會發現其 Unicode code point 和 UTF-16 是一樣的,很容易據此認為它們是等同的概念,實際上這個結論僅僅在雙位元組(UCS-2)時才是成立的,一旦大於兩個位元組,就不成立了,有興趣的可以參考相應的例子

到底 Unicode 和 UTF-8 是什麼關係?一句話:Unicode 是字符集;UTF-8 是編碼

相關文章