一、前言
字符編碼這個問題,困擾了無數(shù)程序員,一不小心就會掉進坑里,每當在開發(fā)中遇到亂碼或者emoji表情符的奇怪問題時總是讓人頭疼不已,本文就來從根源上研究一下字符編碼的本質(zhì)和原理。
二、何為編碼
編碼的本質(zhì)其實就是翻譯,舉個幾個??:
- 兩個說普通話的中國人進行交流時,是不需要編碼的,一個人說「你好」,另一個就能直接聽明白意思,反之亦然。
- 一個中國人和美國人交流時,就需要多一個中文到英文、和英文到中文的翻譯過程,才能實現(xiàn)雙向交流。
- 一個人類和計算機交流時,也是一樣的道理,大家都知道計算機只認識二進制0和1,因此必須把人類語言轉(zhuǎn)為二進制,才能把信息傳遞給計算機,這個過程叫做編碼(encoding),反之則稱為解碼(decoding)。
三、怎么編碼
1. ASCII
一種直觀的想法就是,制作一個映射表,把人類語言和計算機二進制對應(yīng)起來就行了,這樣的思想就孕育出了ASCII編碼(因為是美國人設(shè)計的,所以只有常用英文字符),如下表所示,非常直觀:

2. Unicode
但是ASCII表太小了,最多只能編碼128個字符,但人類語言又那么多,顯然無法滿足需求,于是世界各地的人們就分別設(shè)計了符合自己語言需求的映射表。在自己的地區(qū)使用時是沒有問題的,但是一旦進行國際交流,由于映射規(guī)則各不相同,就會導致混亂。
于是人們設(shè)計了一個很大的映射表——Unicode,總共可以編碼100多萬個字符(最多到 0x10FFFF),目的是為了容納世界上所有的人類語言。Unicode為每個字符分配了一個固定的數(shù)值,稱為編碼點(Code Point),這個值是全局唯一的。而且Unicode向前兼容 ASCII,原先在 ASCII 中定義的字符映射,在 Unicode 中也是一模一樣的。
3. UTF-8
按理說設(shè)計好了 Unicode,大家都按照 Code Point 編碼解碼,混亂的問題就已經(jīng)解決了,但是還有一個因素要考慮,就是存儲成本。
Unicode設(shè)計是三個字節(jié),如果不加考慮直接存儲 Code Point,那么所有的字符都需要占用三個字節(jié)。比如 'A',如果使用ASCII,則一個字節(jié)即可 0x41,如果使用 Unicode,則要編碼為 0x000041,這憑空多出來兩個字節(jié),對于英語文本來說完全是一個額外的存儲開銷。
為了降低存儲成本,人們發(fā)明了變長編碼,這里要注意下,并不是重新定義映射規(guī)則,還是用的 Unicode 的定義,只是以一種更加靈活的形式來存儲以節(jié)約空間。以目前通用性最高的 UTF-8 為例,原本只需要一個字節(jié)的 ASCII 字符,仍然只占一個字節(jié),而像中文及日語這樣的復雜字符就需要2個到3個字節(jié)來存儲。
UTF-8 的規(guī)則挺簡單的:
- 對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個符號的 Unicode 碼。因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的。
- 對于n字節(jié)的符號(1 < n <= 4),第一個字節(jié)的前n位都設(shè)為1,第n + 1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。
維基百科的一覽表:
| Number of bytes | Bits for code point | First code point | Last code point | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
|---|---|---|---|---|---|---|---|
| 1 | 7 | U+0000 | U+007F | 0xxxxxxx | |||
| 2 | 11 | U+0080 | U+07FF | 110xxxxx | 10xxxxxx | ||
| 3 | 16 | U+0800 | U+FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
| 4 | 21 | U+10000 | U+10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
四、Emoji
??????……這些表情符大家應(yīng)該都已經(jīng)不陌生了,但它們并不是什么新的概念,其實也是 Unicode 的一部分,比如這個表情 ??的Code Point是:0x1F600,查上面的 UTF-8 編碼表可以看出需要4個字節(jié)來編碼:0xF09F9880。完整 emoji 列表可以參見這里:https://unicode.org/emoji/charts/full-emoji-list.html
。
MySQL emoji問題:
UTF-8 是能支持所有的 Unicode 字符的,因此按理說 emoji 不應(yīng)該會導致問題,但是在MySQL里實現(xiàn)的 utf8 最長只使用3個字節(jié),如果向一個編碼為 utf8 的列中插入一個表情符時,就會報類似Incorrect string value: '\xF0\x9F\xA6\x96 ...' for column 'name'
這樣的錯,可以看出此時想要插入的值已經(jīng)有四個字節(jié)了,因此需要指定該列編碼格式為 utf8mb4 才行。
五、參考資料
http://cenalulu.github.io/linux/character-encoding/
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
https://en.wikipedia.org/wiki/ASCII
https://en.wikipedia.org/wiki/Unicode
https://en.wikipedia.org/wiki/UTF-8