字元編碼:Unicode & UTF-16 & UTF-8

Journing發表於2023-01-01

ASCII碼

使用一個位元組(8位),對128個字元進行編碼;

最高位始終為0;

碼數範圍為0000_0000(0x00)0111_1111(0x7F)

Unicode

開始的編碼設計

使用兩個位元組(16位),對65536個字元進行編碼;

範圍為0000_0000_0000_0000(0x0000)1111_1111_1111_1111(0xFFFF)

0x0000 - 0x007F對應的字元,與ASCII碼保持一致;

最終的編碼設計

由於世界上的字元,超過了65536個,所以開始只用兩個位元組的設計已經不足夠了,需要擴充套件;

最終擴充套件如下:

  • 基本多語言平面(BMP, Basic Multilingual Plane)

    和開始的設計一致,用兩個位元組來編碼,碼數範圍0x0000 - 0xFFFF

    但是,在這個範圍裡,有預留0xD800 - 0xDFFF的碼數,他們不代表任何字元,僅用於作為增補平面的代理對而存在;

  • 增補平面(SP, Supplementary Plane)

    超出BMP所能表示的字元,改用如下範圍:0x10000 - 0x10FFFF來編碼;

    Unicode編者認為這個範圍已經足夠全世界的字元編碼了,因為這足夠表示一百萬多個字元了;

代理對(surrogate pair)

預留的0xD800 - 0xDFFF,分為兩部分:

  • 高位0xD800 - 0xDBFF
  • 低位0xDC00 - 0xDFFF

這樣做的目的,是為了UTF-16編碼方式;

一個高位加一個低位,共四個位元組,定義了SP中的字元的UTF-16編碼;

碼點(code point)

Unicode編碼中,一個字元所對應的碼數,稱為該字元的碼點;

通常在計算機的字元和字串中,使用\u碼點的形式來轉義碼點,來表示一個Unicode編碼的碼點所對應的字元;

UTF-16

請注意,Unicode編碼的碼點,是人為約定的對字元的編碼方式;

但是計算機只認二進位制,所以如何將Unicode定義的字元的碼點,編碼為計算機實際儲存的二進位制串,以及如何從一串二進位制串,解碼成Unicode定義的字元的碼點,就是UTF-16要做的事情;

UTF-16的16代表最小的編碼單位是16位二進位制串;

編碼

分為兩種情況:

  • BMP中的字元

    直接用Unicode定義的碼點作為UTF-16編碼即可;

  • SP中的字元

    使用兩個16位二進位制串進行編碼,即採用四個位元組來編碼;

    現在假設有一個字元,其Unicode定義的碼點為0xAAAAA,對其進行如下操作:

    • u = 0xAAAAA - 0x10000;
    • 將u寫成二進位制串:yyyy_yyyy_yyxx_xxxx_xxxx
    • 則該字元的UTF-16編碼為:1101_10yy_yyyy_yyyy 1101_11xx_xxxx_xxxx

    SP的UTF-16編碼的兩個16位二進位制串:

    第一個16位串的前六位固定是1101_10,結合yy的範圍(00 - 11),即1101_1000 - 1101_1011,此範圍即是代理對的高位的前兩位0xD8 - 0xDB

    第二個16位串的前六位固定是1101_11,結合xx的範圍00 - 11,即1101_1100 - 1101_1111,此範圍即是代理對的低位的前兩位的範圍0xDC - 0xDF

    再結合各自後面八位二進位制串的範圍0000_0000 - 1111_1111,就可以得到各自完整的代理對;

    也就是說,SP的UTF-16的編碼結果,即為高位+低位的四個位元組的代理對;

解碼

只要看一個16位二進位制串的頭八位,是否在代理對的範圍即可;

  • 不在代理對的範圍

    說明是BMP中的字元,直接對應Unicode碼點找到對應的字元即可;

  • 在代理對的範圍

    說明是SP中的字元,再根據頭六位確定好代理對的高低位,

    去除各自的前六位,組成20位二進位制串,再加上0x10000即為Unicode定義的碼點,即可找到對應的字元;

UTF-8

UTF-8是不同於UTF-16的另一種對Unicode的編解碼方式;

不同之處就在於,UTF-8的8代表最小的編碼單位是8位二進位制串;

編碼

UTF-8對碼點的編碼方式如下:

  • 碼點範圍0x0000 - 0x007F

    UTF-8編碼為二進位制串0xxx_xxxx,與ASCII碼保持一致,長度為1個位元組;

  • 碼點範圍0x0080 - 0x07FF

    UTF-8編碼為二進位制串110x_xxxx 10xx_xxxx,長度為2個位元組;

  • 碼點範圍0x0800 - 0xFFFF

    UTF-8編碼為二進位制串1110_xxxx 10xx_xxxx 10xx_xxxx,長度為3個位元組;

  • 碼點範圍0x10000 - 0x10FFFF

    UTF-8編碼為二進位制串1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx,長度為4個位元組;

假設現在有一個字元,碼點在範圍0x0800 - 0xFFFF中:

  • 將其碼點寫成二進位制串:xxxx_yyyy yyzz_zzzz
  • 則UTF-8編碼的第一個位元組為1110_xxxx;
  • 第二個位元組為10yy_yyyy
  • 第三個位元組為10zz_zzzz

解碼

只要看第一個位元組的首位即可:

  • 首位為0

    說明在碼點範圍0x0000 - 0x007F,直接對應Unicode碼點找到對應的字元即可;

  • 首位為1,再看從首位開始,遇到第一個0結束,一共有幾個1

    • 兩個1,說明UTF-8編碼長度為2個位元組
    • 三個1,說明UTF-8編碼長度為3個位元組
    • 四個1,說明UTF-8編碼長度為4個位元組
    • 去除對應位元組的固定位,組合為一個二進位制串,找到對應Unicode碼點的字元即可;

程式碼單元(code unit)

不同的UTF編碼,所對應的編碼單位的長度不同;

UTF-16的編碼單位的長度為16位二進位制;

UTF-8的編碼單位的長度為8位二進位制;

這個編碼單位稱為程式碼單元;

比如對於UTF-16的編碼:

BMP中,一個字元所對應的UTF-16的16位二進位制串,稱為該字元的程式碼單元;

而在SP中,一個字元所對應的UTF-16的兩個16位二進位制串,稱為該字元的一對程式碼單元;

而對於UTF-8的編碼:

在碼點範圍0x0000 - 0x007F中,一個字元所對應的UTF-8的4個位元組,稱為該字元的4個程式碼單元;

在碼點範圍0x0080 - 0x07FF中,一個字元所對應的UTF-8的4個位元組,稱為該字元的4個程式碼單元;

在碼點範圍0x0800 - 0xFFFF中,一個字元所對應的UTF-8的4個位元組,稱為該字元的4個程式碼單元;

在碼點範圍0x10000 - 0x10FFFF中,一個字元所對應的UTF-8的4個位元組,稱為該字元的4個程式碼單元;

也就是說,隨著UTF編碼形式的不同,同一個字元的碼點,會有不同個數的程式碼單元;

相關文章