UTF-8:一些好像沒什麼用的冷知識

原創felix021felix021_3月29日_javascript

本文一共 3023 字,閱讀時間我不太會算。

在喬納森·斯威夫特的著名諷刺小說《格列夫遊記》中,小人國內部分裂成Big-endian和Little-endian兩派,區別在於一派要求從雞蛋的大頭把雞蛋打破,另外一派要求從雞蛋的小頭把雞蛋打破。html

而後忘了這個故事,我們開始吧。java

Charles同窗這周踩了個坑,數據插入MySQL時報錯:mysql

1366 Incorrect string value: 
'\xF0\x90\x8D\x83...' for column 'content' at row 1

按慣例搜一下,聽說是由於mysql用的 utf8 不支持 emoji,須要修改配置文件,將字符集改爲 utf8mb4:程序員

[mysqld]
character-set-server = utf8mb4
stackoverflow.com/questions/10957238

可是 Charles 同窗已經給 MySQL 加上了這個配置,仍然報錯。面試

image.png

實際上,MySQL 還有另一個配置,用於指定客戶端和服務器之間鏈接時使用的字符集:sql

[mysqld]
character-set-client-handshake = utf8mb4

固然,也能夠在MySQL Client中指定,具體須要參考client的文檔,或者簡單粗暴地在鏈接成功之後執行(但不推薦):vim

SET NAMES utf8mb4;

utf8 和 utf8mb4

那麼,什麼是utf8mb4?和utf8有啥區別呢?windows

根據MySQL的manual:後端

The utfmb4 character set has these characteristics: 
- Supports BMP and supplementary characters.
- Requires a maximum of four bytes per multibyte character.

https://dev.mysql.com/doc/ref...

(文檔中utf8mb4打錯了,我是原樣複製的)

翻譯過來就是,utf8mb4 支持BMP(Unicode Basic Multilingual Plane)和補充字符,每一個字符最多 4 字節(這裏 「mb4」 大概就是 multi byte 4 的簡寫了)。

冷知識:Unicode編碼一共有 17 個 "Plane"(0~16),其中 Plane 0 就是BMP,包含絕大多數經常使用字符,好比希臘、希伯來、阿拉伯、CJK(Chinese-Japanese-Korean)字符等。Plane 1~16 被稱爲 "supplementary planes",包含不經常使用的其餘字符,例如 emoji 和某些特殊的CJK字符。因此目前 Unicode 字符的最大編碼爲 0x10FFFF。

至於 utf8,MySQL文檔裏也有說明:

utf8 is an alias for the utf8mb3 character set. 

Note
The utf8mb3 character set is deprecated and will be removed in a future MySQL release. Please use utf8mb4 instead

https://dev.mysql.com/doc/ref...

簡單說就是掛羊頭賣狗肉了,看到的是 utf8,實際用的是 utf8mb3 。

utf8mb3 的文檔就不貼了(懶),和 ut8mb4 的區別就在於最多隻支持3個字節,所以不支持Unicode的補充字符集。

也就是說,MySQL裏的utf8,其實是一個閹割版的utf8。

MySQL 從  5.5.3 纔開始支持完整版的 utf8(utf8mb4),而且後續計劃移除 utf8mb3,utf8 將來在mysql中也會變成 utf8mb4 的別名,因此之後默認都使用 utf8mb4 就對了。

話說回來,MySQL爲何會有這種奇怪的設定呢?

其實最初是從性能上考慮的,這個精簡版的 utf8 在運行的時候能夠更快一點點。

要知道 MySQL 已是一個 24 歲的老項目了,在1995年誕生時,Intel 才只推出了 Pentium Pro,對比如今的CPU,性能能夠說是很是差了。

冷知識:差到什麼程度呢?舉個例子,早期的 Windows Beta 版,桌面右下角時間是能夠顯示秒數的,但因爲當時硬件的性能問題,微軟在發佈 Windows 95 以前就移除了該功能,直到Windows 7(2009年)才容許經過修改註冊表開啓。

真正的utf8

那麼真正的 utf8 長什麼樣呢?

在查文檔以前,不妨先動手建立一個文檔看一下

$ echo '0Aa你好' > utf-8.txt

$ file utf-8.txt
utf-8.txt: UTF-8 Unicode text

$ xxd utf-8.txt #用16進制的方式查看
0000: 3041 61e4 bda0 e5a5 bd0a      0Aa.......

能夠看到,開頭"0Aa" 對應3個字節 0x30、0x4一、0x61(十進制4八、6五、97,大寫A < 小寫a 就是這麼來的)。

最後一個字符 0x0a 是  echo  默認輸出的換行。

冷知識:能夠加上 -n 參數讓 echo 不輸出換行符。換行符在不一樣OS下不一樣,在Linux/Unix下是 "\n" (0x0a),在Windows下是 "\r\n"(0x0d 0x0a),在早期Mac下是 "\r" (0x0d),從Mac OS 10.0(2001)開始也和Unix同樣用 "\n" 了。在C/Python等語言下,fopen/open默認使用「文本模式」打開文件,讀取時會統一轉換成 "\n",寫入時將"\n"轉換爲按os的默認值。還有一些其餘場景可能須要注意,例如http協議中header使用 "\r\n" 換行。

中間的 "e4bda0e5a5bd" 這 6 個字節對應的就是 「你好」 了,每一個字符 3 個字節。

那到底如何肯定一個 utf8 字符是幾個字節呢?

這裏貼一個 wikipedia 的表格:

image.png

簡單解釋一下:

  • 第一個字節中,開頭 1 的數量代表了這個utf8字符包含幾個字節
  • 0開頭,表示只須要1個字節,剩餘7 bit能夠表示unicode中的0~127,正好和 ascii 編碼兼容。
  • 110 開頭,表示須要2個字節,包含 11  bit,能夠表示大部分非 CJK 字符(希臘、阿拉伯等字符)
  • 1110 開頭,表示須要3個字節,包含 16 bit,正好能夠表示全部BMP的字符,好比 「你好」都在 這個Plane裏面,因此一共須要6個字節。
  • 11110 開頭,表示須要4個字節,包含 21 bit,最多能夠包含 32 個 Plane,超過了當前 17 個 Plane 的
  • 後續字符都是 10 開頭,不會和首字符混淆,所以在解析的時候很容易識別,就算遇到了錯誤的編碼字符(例如按字節截斷到字符中間),也能夠簡單跳過,定位下一個字符。

前面展現了 ASCII 字符和中文,我們順便再看看 emoji 的 utf-8 編碼長什麼樣:

$ echo -n 😀 > 1.txt

$ xxd 1.txt
0000: f09f 9880    ....

根據上面的表格,咱們能夠算出,這個GRIN FACE(露齒笑)的 Unicode 碼點是 0x1F600,在Unicode的Plain 1中,所以utf-8編碼須要4個字節。

字符集和編碼規範

上文提到了 Unicode  和 utf-8 這兩個名詞,可是不少同窗其實沒搞明白他倆的區別是啥。

通常咱們提到 Unicode 時指的是字符集(Character Set),其中包含了一系列字符,併爲每個字符指定了碼點(Code Point,即該字符的編號)。

Unicode標準裏包含了不少編碼規範,utf-8 是其中一種,指定了一個Unicode字符的碼點應該如何存儲,例如ASCII用一個字節,超過一個字節的根據須要的bit數量,分別存儲到多個字節中。

除了 utf-8 以外,Unicode還有多種不一樣的編碼規範,例如

  • ucs-2,就是簡單地一一對應 BMP 的編碼,每一個字符使用2個字節,所以不支持補充字符。
  • ucs-4,用4個字節來存儲 unicode 編碼,所以支持Unicode中的全部plain。Unicode後續的修訂也會保證添加的碼點都在31bit範圍內。
  • utf-16,BMP內的字符等於ucs-2,其餘plane用4個字節表示
  • windows和ecma規範(javascript)使用utf-16做爲其內部編碼。
  • utf-32,ucs-4的子集,通常能夠認爲就是ucs-4。

然鵝 utf-8 幾乎統治了互聯網,超過93%的網頁是用UTF-8編碼的,以致於IETF要求全部網絡協議都須要標明內容的編碼,而且必須支持UTF-8。

至於緣由麼,還記得開頭的那個故事嗎?utf-8避免了上述編碼中的字節序(big endian、little endian)的問題。

固然這只是一個緣由,我認爲更重要的是,utf-8保持了對ascii的兼容,路徑依賴的強大慣性,會致使上述4種編碼在實際推廣中帶來很高的遷移成本(按理應該在這裏講講馬屁股寬度的故事,不過跑題太遠了)。

utf-8 在保持後向兼容的前提下,能支持全部Unicode字符,相比ucs4還能節省大量存儲空間、數據傳輸量,所以統治了互聯網,也就在情理之中了。

除了Unicode以外,還有不少其餘字符集,例如最經典的ASCII,因爲字符少,其編碼規範也至關簡單。

在中國,比較常見的字符集還有GB2312(1980年)、GBK(1993年)、GB18030(2000年),這些標準都規定了對應的編碼規範,因此這些名字既能夠表示字符集,也能夠表示編碼規範。

其中GB2312只包含 7445 個字符,其中漢字 6763 個(只包含經常使用漢字,不少生僻字都不支持),編碼規範也兼容ASCII。GBK(GB13000)兼容GB2312,添加了更多字符,GB18030是進一步的補充。

冷知識:咱們可使用 iconv 命令行工具來修改文件的字符編碼

$ iconv -f gb18030 -t utf-8 < gb18030.txt 
0Aa你好

也能夠在vim中這麼幹

:set fileencoding=gb18030

此外,使用 windows 的同窗可能還見到過一個奇怪的代號 "cp936"(在上述iconv命令、vim中均可以使用),這是微軟對 GB2312 的實現,在 Win95 之後實際上也支持了大部分 GBK 字符。

總結

  1. Unicode是一個字符集,包含17個Plane,每一個Plane 65536個碼點,Plane0是BMP,其餘Plane是補充字符
  2. UTF-8是一種編碼規範,用1~4個字節編碼Unicode字符,兼容ASCII,中文3字節,補充字符如emoji須要4字節)
  3. MySQL中的utf8是閹割版的、只支持BMP的編碼,之後記得都使用utf8mb4;除了server編碼,記得也要修改鏈接的編碼(客戶端編碼)。
  4. 除了utf-8以外,還有好幾個沒什麼卵用的字符集/編碼。
  5. 我在網盟廣告業務線(穿山甲),因爲業務持續高速發展,長期缺人。關於字節跳動面試的詳情,可參考我以前寫的《程序員面試指北:面試官視角

~ 投遞連接 ~

後端開發(上海)
https://job.toutiao.com/s/sBAvKe

後端開發(北京)
https://job.toutiao.com/s/sBMyxk

廣告策略研發(上海)
https://job.toutiao.com/s/sBDMAK

全部職位
https://job.toutiao.com/s/sB9Jqk

歡迎關注

微信掃碼
image.png

相關文章
相關標籤/搜索