Skip to content

Latest commit

 

History

History
107 lines (74 loc) · 4.59 KB

unicode.rst

File metadata and controls

107 lines (74 loc) · 4.59 KB

= Unicode 與 Python 筆記 =

來源

其實我從來沒寫過需要處理編碼的程式,所以這份筆記我不會用到,只是覺得很有趣所以記下來。

資料來源 http://codeblog.niwyclin.org/posts/196511-unicode-in-python,感謝 wdv4758h 的分享。

Before Unicode

  • ASCII code

    定義了 0 ~ 127 的字元

  • ISO Latin 1 (ISO 8859-1)

    定義 0 ~ 255 的字元

Unicode

重要觀念: Unicode 不是編碼方式,只是把字元編號而已,沒有定義每個字元實際上該怎麼存

例如 U+2602 字元,只代表一個「Unicode 編號 2602(16 進位)」的字元,該怎麼存在電腦裡,隨便

U+2602 被稱為 code point 2602

目前 unicode 已定義 110122 個字元(不含控制字元等等)

UTF-16

並不是「總長度為 16 bit」,而是「一個編碼單位的長度就是 16 bit」

UTF-16 的子集 UCS-2 才是「固定只用 16 bit 編碼」,所以只能表示 0 ~ 65535 的字元

  • before U+FFFF
    • 在 U+FFFF 以前的字元,就直接用 16 bit 來表示,不足的部份補 0
  • after U+FFFF
    • 從 U+10000 開始是 16 bit 無法表示的範圍,需要 32 bit 表示,方法如下:
      1. 把 code point 編號減去 10000(16 進位),會產生 000000 ~ 0FFFFF 的值,總共 20 bit,稱為 a
      2. a 的前 10 bit 加上 D800 (16 進位),稱為 a1a1 的值會落在 D800 ~ DBFF 之間
      3. a 的後 10 bit 加上 DC00 (16 進位),稱為 a2a1 的值會落在 DC00 ~ DFFF 之間
    • Unicode 中的 U+D800 ~ U+DFFF 有被刻意保留不定義任何字元,所以用這種編碼方式表示的資料,每個 byte 都可以單獨分辨是第一或是第二個編碼單位
    • 範例: U+1030A
      1. code point 減去 10000(16),得到 30A,2 進位為 1100001010,不足 20 位元的部分補零,變成 00000000001100001010
      2. 前 10 位元為 0000000000 ,換成 16 進位為 0000 ,加上 D800 得到 D800
      3. 後 10 位元為 1100001010 ,換成 16 進位為 030A ,加上 DC00 得到 DF0A
      4. 得到結果為 U+1030A -> D800DF0A

UTF-32

UTF-32 以 32 bit 為一個單位,已經超過 Unicode 的總量,故規則比 UTF-16 簡單,直接把 code point 補成 32 bit 的 binary number

UTF-8

UTF-16 和 UTF-32 的概念簡單,但沒有向下相容,傳統的 ASCII 字元到了 UTF-16/UTF-32 需要增加成 16/32 個 bit,傳統的檔案無法直接開啟,故出現了 UTF-8

UTF-8 讓每個字元都可以分別用不同的長度編碼,如果是傳統的 ASCII 字元,就使用原本的編碼,故傳統 ASCII 編碼的檔案也可以直接開啟

  • UTF-8 的解碼方式
    1. 如果資料的開頭 bit 是 0,表示這個 byte 是 ASCII 編碼的字元
    2. 如果資料的開頭 bit 是 1,表示這個 byte 是 UTF-8 編碼的一部份,進入以下判斷
      1. 如果資料的開頭 bit 是 10,表示 byte 是中間的 byte,不是開頭
      2. 如果資料的開頭 bit 是 110/1110/11110/1*n0,表示這個字元用到 2/3/4/n 個 byte,而且這個 byte 是開頭
      3. 把以上用來判斷 UTF-8 相關的 bit 去除以後,剩下的 bit 接在一起,組成 code point
  • 範例: 00100011 11100101 10100100 10101001
    1. 00100011 的開頭是 0 ,所以它是 ASCII char,查表發現是 #
    2. 11100101 的開頭是 111 ,所以這個字元佔了 3 個 byte,這個 byte 的資料部份是 00101
    3. 10100100 的開頭是 10 ,由上一步也預測了這點,這個 byte 的資料部份是 100100
    4. 10101001 的開頭是 10 ,由上上步也預測了這點,這個 byte 的資料部份是 101001
    5. 00101, 100100, 101001 組合成 00101100100101001 ,即為 U+5929 ,查表發現是
    6. 解碼 00100011 11100101 10100100 10101001 的結果為 #天

Unicode 與 Python

  • Python2

    >>> my_unicode_str = u"Hi \u2119\u01b4\u2602\u210c\u00f8\u1f24"
    >>> len(my_unicode)
    9
    >>> my_utf8_str = my_unicode_str.encode('utf-8')
    >>> len(my_utf8)
    19
    >>> my_utf8
    'Hi \xe2\x84\x99\xc6\xb4\xe2\x98\x82\xe2\x84\x8c\xc3\xb8\xe1\xbc\xa4'
    >>> my_utf8.decode('utf-8')
    u"Hi \u2119\u01b4\u2602\u210c\u00f8\u1f24"
    
    • Python2 常常會偷轉編碼,很容易壞掉
  • Python3

    • 在 Python3 裡, str 存的是 Unicode code point, byte 存的是編碼後的字串
    • Unicode str 經過 encode() 之後會變成 byte
    • byte 可以 decode() 回 Unicode str