理解ASCII、Unicode、utf-8、utf-16、utf-32
目錄
理解ASCII、Unicode、utf-8、utf-16、utf-32編碼與解碼字符集字符編碼ASCIIUnicodeUTFUTF-32UTF-16UTF-8UTF-8的編碼實作方法Emoji問題
編碼與解碼
在計算機中,資訊是由 0和1組成的二進制 進行傳遞的,將我們看到的字符轉化為二進制數字的程序就是編碼,反之將二進制數字轉換為字符的程序是解碼,
字符集
ASCII和Unicode分別是兩種不同的編碼方式,通過某一種編碼方式進行編碼所組成的集合稱為字符集,如,字符通過Unicode編碼所組成集合稱為Unicode字符集,
字符編碼
對于一個字符集來說要正確編碼轉碼一個字符需要三個關鍵元素:字庫表(character repertoire)、編碼字符集(coded character set)、字符編碼(character encoding form),
字庫表,決定了整個字符集能夠展現表示的所有字符的范圍,
編碼字符集,即用一個編碼值來表示一個字符在字庫中的位置,如Unicode,
字符編碼,將編碼字符集和實際存盤數值之間的轉換關系,一般會將編碼字符集的值作為編碼后的值直接存盤,如UTF-8,
看到這里,可能很多讀者都會有和我當初一樣的疑問:
字庫表和編碼字符集看來是必不可少的,那既然字庫表中的每一個字符都有一個自己的序號,直接把序號作為存盤內容就好了,為什么還要多此一舉通過字符編碼把序號轉換成另外一種存盤格式呢?其實原因也比較容易理解:統一字庫表的目的是為了能夠涵蓋世界上所有的字符,但實際使用程序中會發現真正用的上的字符相對整個字庫表來說比例非常低,例如中文地區的程式幾乎不會需要日語字符,而一些英語國家甚至簡單的ASCII字庫表就能滿足基本需求,而如果把每個字符都用字庫表中的序號來存盤的話,每個字符就需要3個位元組(這里以Unicode字庫為例),這樣對于原本用僅占一個字符的ASCII編碼的英語地區國家顯然是一個額外成本(存盤體積是原來的三倍),算的直接一些,同樣一塊硬碟,用ASCII可以存1500篇文章,而用3位元組Unicode序號存盤只能存500篇,于是就出現了UTF-8這樣的變長編碼,在UTF-8編碼中原本只需要一個位元組的ASCII字符,仍然只占一個位元組,而像中文及日語這樣的復雜字符就需要2個到3個位元組來存盤,
ASCII
由于計算機是美國人發明的,并沒有考慮其他國家的字符,最早的編碼方式便是ASCII,0 - 255被用來表示大小寫英文字母、數字和一些符號,這個編碼表被稱為ASCII編碼,
Unicode
Unicode編碼定義了這個世界上幾乎所有字符,而且Unicode還兼容了很多老版本的編碼規范,例如剛剛講過的 ASCII碼,Unicode 編碼 發展到今天 擴展到了 21 位,詳見http://en.wikipedia.org/wiki/Unicode
Unicode也為了每個字符發了一張身份證,這張“身份證”上有一串唯一的數字ID確定了這個字符,這串數字在整個計算機的世界具有唯一性,Unicode給這串數字ID起了個名字叫 碼點 ,碼點 經過映射后得到的二進制串的轉換格式單位稱之為 碼元,碼點就是一串二進制數,碼元 就是切分這個二進制數的方法,
Unicode的編號從 0000開始一直到 10FFFF共分為16個Plane,每個Plane中有65536個字符(正好填充2個位元組,16位),0 號平面叫做基本多文種平面( BMP, Basic Multilingual Plane ),涵蓋了幾乎所有你能遇到的字符,除了 emoji(emoji位于1號平面 ),其它平面叫做補充平面,大多是空的,
UTF
Unicode轉換格式(Unicode Transformation Formats,即UTF),是為了解決碼點在計算機中存盤方式而設計的,
UTF-32
UTF-32是最好理解的一個了,UTF-32也就是說它的碼元是32位,每32位去讀一下碼點,而碼點是Unicode給字符的編碼,前面也說了,最長才21位,因此每一個 UTF-32 值都可以直接表示對應的碼點,
UTF-16
UTF-16的碼元是16位的,也就是說每16位去讀一下碼點,獲取碼點的前16位數字,直到讀取完成,由于BMP 幾乎包括了所有常見字符,UTF-16 一般需要 UTF-32大約 一半的空間,至于其它平面里很少使用的碼點都是用兩個 16 位的碼元來編碼的,
UTF-8
UTF-8 使用一到四個位元組來編碼一個碼點,從 0 到 127 的這些碼點直接映射成 1 個位元組(對于只包含這個范圍字符的文本來說,這一點使得 UTF-8 和 ASCII 完全相同),接下來的 1,920 個碼點映射成 2 個位元組,在 BMP 里所有剩下的碼點需要 3 個位元組,Unicode 的其他平面里的碼點則需要 4 個位元組,UTF-8 是基于 8 位的碼元的,UTF-8 是基于 8 位的碼元的,因此它并不需要關心位元組順序(因為位元組就是8位的呀,其它UTF-16和UTF-32在不同的機器編譯環境下需要考慮位元組的順序問題)
UTF-8的編碼實作方法
UTF-8編碼為變長編碼,最小編碼單位為一個位元組,一個位元組的前1-3個bit為描述性部分,后面為實際序號部分,
如果一個位元組的第一位為0,那么代表當前字符為單位元組字符,占用一個位元組的空間,0之后的所有部分代表在Unicode中的序號,
如果一個位元組以110開頭,那么代表當前字符為雙位元組字符,占用2個位元組的空間,110之后的所有部分代表在Unicode中的序號,且第二個位元組以10開頭
如果一個位元組以1110開頭,那么代表當前字符為三位元組字符,占用3個位元組的空間,110之后的所有部分代表在Unicode中的序號,且第二、第三個位元組以10開頭
如果一個位元組以10開頭,那么代表當前位元組為多位元組字符的第二個位元組,10之后的所有部分代表在Unicode中的序號,
具體每個位元組的特征可見下表,其中 x代表序號部分,把各個位元組中的所有 x部分拼接在一起就組成了在Unicode字庫中的序號
| Byte1 | Byte2 | Byte3 |
|---|---|---|
| 0xxx xxxx | ||
| 110x xxxx | 10xx xxxx | |
| 1110 xxxx | 10xx xxxx | 10xx xxxx |
可以得出這樣一個規律:
3個位元組的UTF-8十六進制編碼一定是以
E開頭的2個位元組的UTF-8十六進制編碼一定是以
C或D開頭的1個位元組的UTF-8十六進制編碼一定是以比
8小的數字開頭的
Emoji問題
前面也提到Emoji位于1號平面,是需要四個位元組來編碼的,Emoji在Unicode中位于 \u1F601- \u1F64F區段,這個顯然超過了目前常用的UTF-8字符集的編碼范圍 \u0000- \uFFFF,Emoji表情隨著IOS的普及和微信的支持越來越常見,下面就是幾個常見的Emoji:
??????
那么Emoji字符表情會對我們平時的開發運維帶來什么影響呢?最常見的問題就在于將他存入MySQL資料庫的時候,一般來說MySQL資料庫的默認字符集都會配置成UTF-8(三位元組),而utf8mb4在5.5以后才被支持,也很少會有DBA主動將系統默認字符集改成utf8mb4,那么問題就來了,當我們把一個需要4位元組UTF-8編碼才能表示的字符存入資料庫的時候就會報錯:ERROR1366:Incorrectstringvalue:'\xF0\x9D\x8C\x86'forcolumn ,如果認真閱讀了上面的解釋,那么這個報錯也就不難看懂了,我們試圖將一串Bytes插入到一列中,而這串Bytes的第一個位元組是 \xF0意味著這是一個四位元組的UTF-8編碼,但是當MySQL表和列字符集配置為UTF-8的時候是無法存盤這樣的字符的,所以報了錯,
那么遇到這種情況我們如何解決呢?有兩種方式:
升級MySQL到5.6或更高版本,并且將表字符集切換至utf8mb4,
第二種方法就是在把內容存入到資料庫之前做一次過濾,將Emoji字符替換成一段特殊的文字編碼,然后再存入資料庫中,之后從資料庫獲取或者前端展示時再將這段特殊文字編碼轉換成Emoji顯示,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/59948.html
標籤:其他
