Unicode、UTF-8、UTF-16,終於懂了 - 閱坊

文章推薦指數: 80 %
投票人數:10人

Unicode 字符集的編碼範圍是 0x0000 - 0x10FFFF , 可以容納一百多萬個字符, 每個字符 ... 下表是Unicode 編碼對應UTF-8 需要的字節數量以及編碼格式. ✕ Rust 架構 機器學習 Java 大數據 Javascript Go 微服務 DDD Twitter Facebook Feed 服務協議 聯繫我們 計算機起源於美國,上個世紀,他們對英語字符與二進制位之間的關係做了統一規定,並制定了一套字符編碼規則,這套編碼規則被稱爲ASCII編碼 ASCII編碼一共定義了128個字符的編碼規則,用七位二進制表示(0x00-0x7F),這些字符組成的集合就叫做ASCII字符集 隨着計算機的普及,在不同的地區和國家又出現了很多字符編碼,比如:大陸的GB2312、港臺的BIG5,日本的ShiftJIS等等 由於字符編碼不同,計算機在不同國家之間的交流變得很困難,經常會出現亂碼的問題,比如:對於同一個二進制數據,不同的編碼會解析出不同的字符 當互聯網迅猛發展,地域限制打破之後,人們迫切的希望有一種統一的規則,對所有國家和地區的字符進行編碼,於是Unicode就出現了 Unicode簡介 Unicode是國際標準字符集,它將世界各種語言的每個字符定義一個唯一的編碼,以滿足跨語言、跨平臺的文本信息轉換 Unicode字符集的編碼範圍是 0x0000-0x10FFFF ,可以容納一百多萬個字符,每個字符都有一個獨一無二的編碼,也即每個字符都有一個二進制數值和它對應,這裏的二進制數值也叫 碼點 ,比如:漢字 "中" 的碼點是 0x4E2D,大寫字母 A 的碼點是 0x41,具體字符對應的Unicode編碼可以查詢 Unicode字符編碼表 字符集和字符編碼 字符集是很多個字符的集合,例如GB2312是簡體中文的字符集,它收錄了六千多個常用的簡體漢字及一些符號,數字,拼音等字符 字符編碼是字符集的一種實現方式,把字符集中的字符映射爲特定的字節或字節序列,它是一種規則 比如:Unicode只是字符集,UTF-8、UTF-16、UTF-32纔是真正的字符編碼規則 Unicode字符存儲 Unicode是一個符號集,它只規定了每個符號的二進制值,但是符號具體如何存儲它並沒有規定 前面提到,Unicode字符集的編碼範圍是 0x0000-0x10FFFF,因此需要1到3個字節來表示 那麼,對於三個字節的Unicode字符,計算機怎麼知道它表示的是一個字符而不是三個字符呢? 如果所有字符都用三個字節表示,那麼對於那些一個字節就能表示的字符來說,有兩個字節是無意義的,對於存儲來說,這是極大的浪費,假如,一個普通的文本,大部分字符都只需一個字節就能表示,現在如果需要三個字節才能表示,文本的大小會大出三倍左右 因此,Unicode出現了多種存儲方式,常見的有UTF-8、UTF-16、UTF-32,它們分別用不同的二進制格式來表示Unicode字符 UTF-8、UTF-16、UTF-32中的"UTF"是"UnicodeTransformationFormat"的縮寫,意思是"Unicode轉換格式",後面的數字表明至少使用多少個比特位來存儲字符,比如:UTF-8最少需要8個比特位也就是一個字節來存儲,對應的,UTF-16和UTF-32分別需要最少2個字節和4個字節來存儲 UTF-8編碼 UTF-8:是一種變長字符編碼,被定義爲將碼點編碼爲1至4個字節,具體取決於碼點數值中有效二進制位的數量 UTF-8的編碼規則: 對於單字節的符號,字節的第一位設爲 0,後面7位爲這個符號的Unicode碼。

因此對於英語字母,UTF-8編碼和ASCII碼是相同的,所以UTF-8能兼容ASCII編碼,這也是互聯網普遍採用UTF-8的原因之一 對於 n 字節的符號( n>1),第一個字節的前 n 位都設爲 1,第 n+1 位設爲 0,後面字節的前兩位一律設爲 10 。

剩下的沒有提及的二進制位,全部爲這個符號的Unicode碼 下表是Unicode編碼對應UTF-8需要的字節數量以及編碼格式 YPFYCo 表格中第一列是Unicode編碼的範圍,第二列是對應UTF-8編碼方式,其中紅色的二進制 "1" 和 "0" 是固定的前綴,字母 x 表示可用編碼的二進制位 根據上面表格,要解析UTF-8編碼就很簡單了,如果一個字節第一位是 0 ,則這個字節就是一個單獨的字符,如果第一位是 1 ,則連續有多少個 1 ,就表示當前字符佔用多少個字節 下面以 "中" 字爲例來說明UTF-8的編碼,具體的步驟如下圖,爲了便於說明,圖中左邊加了 1,2,3,4 的步驟編號 首先查詢 "中" 字的Unicode碼 0x4E2D,轉成二進制,總共有16個二進制位,具體如上圖步驟1所示 通過前面的Unicode編碼和UTF-8編碼的表格知道,Unicode碼 0x4E2D 對應 000800-00FFFF 的範圍,所以, "中" 字的UTF-8編碼需要 3 個字節,即格式是 1110xxxx 10xxxxxx 10xxxxxx 然後從 "中" 字的最後一個二進制位開始,按照從後向前的順序依次填入格式中的 x 字符,多出的二進制補爲 0,具體如上圖步驟2、步驟3所示 於是,就得到了 "中" 的UTF-8編碼是 11100100 10111000 10101101,轉換成十六進制就是 0xE4B8AD,具體如上圖步驟4所示 UTF-16編碼 UTF-16也是一種變長字符編碼,這種編碼方式比較特殊,它將字符編碼成2字節或者4字節 具體的編碼規則如下: 對於Unicode碼小於 0x10000 的字符,使用 2 個字節存儲,並且是直接存儲Unicode碼,不用進行編碼轉換 對於Unicode碼在 0x10000 和 0x10FFFF 之間的字符,使用 4 個字節存儲,這 4 個字節分成前後兩部分,每個部分各兩個字節,其中,前面兩個字節的前 6 位二進制固定爲 110110,後面兩個字節的前6位二進制固定爲 110111,前後部分各剩餘10位二進制表示符號的Unicode碼減去 0x10000 的結果 大於 0x10FFFF 的Unicode碼無法用UTF-16編碼 下表是Unicode編碼對應UTF-16編碼格式 u1RYaU 表格中第一列是Unicode編碼的範圍,第二列是具體Unicode碼的二進制(第二行的第二列表示的是Unicode碼減去 0x10000 後的二進制),第三列是對應UTF-16編碼方式,其中紅色的二進制 "1" 和 "0" 是固定的前綴,字母 x 和 y 表示可用編碼的二進制位,第四列表示編碼佔用的字節數 前面提到過,"中" 字的Unicode碼是 4E2D,它小於 0x10000,根據表格可知,它的UTF-16編碼佔兩個字節,並且和Unicode碼相同,所以 "中" 字的UTF-16編碼爲 4E2D 我從 Unicode字符表網站 找了一個老的南阿拉伯字母,它的Unicode碼是: 0x10A6F ,可以訪問 https://unicode-table.com/cn/10A6F/ 查看字符的說明,Unicode碼對應的字符如下圖所示 下面以這個老的南阿拉伯字母的Unicode碼 0x10A6F 爲例來說明UTF-16 4 字節的編碼,具體步驟如下,爲了便於說明,圖中左邊加了 1,2,3,4、5的步驟編號 首先把Unicode碼 0x10A6F 轉成二進制,對應上圖的步驟1 然後把Unicode碼 0x10A6F 減去 0x10000,結果爲 0xA6F 並把這個值轉成二進制 0000000010 1001101111,對應上圖的步驟2 然後從二進制 0000000010 1001101111 的最後一個二進制爲開始,按照從後向前的順序依次填入格式中的 x 和 y 字符,多出的二進制補爲 0,對應上圖的步驟3、步驟4 於是,就計算出了Unicode碼 0x10A6F 的UTF-16編碼是 1101100000000010 1101111001101111 ,轉換成十六進制就是 0xD802DE6F,對應上圖的步驟5 UTF-32編碼 UTF-32是固定長度的編碼,始終佔用4個字節,足以容納所有的Unicode字符,所以直接存儲Unicode碼即可,不需要任何編碼轉換。

雖然浪費了空間,但提高了效率。

UTF-8、UTF-16、UTF-32之間如何轉換 前面介紹過,UTF-8、UTF-16、UTF-32是Unicode碼錶示成不同的二進制格式的編碼規則,同樣,通過這三種編碼的二進制表示,也能獲得對應的Unicode碼,有了字符的Unicode碼,按照上面介紹的UTF-8、UTF-16、UTF-32的編碼方法就能轉換成任一種編碼了 UTF字節序 最小編碼單元是多字節纔會有字節序的問題存在,UTF-8最小編碼單元是一字節,所以它是沒有字節序的問題,UTF-16最小編碼單元是2個字節,在解析一個UTF-16字符之前,需要知道每個編碼單元的字節序 比如:前面提到過,"中" 字的Unicode碼是 4E2D, "ⵎ" 字符的Unicode碼是 2D4E,當我們收到一個UTF-16字節流 4E2D 時,計算機如何識別它表示的是字符 "中" 還是字符 "ⵎ" 呢? 所以,對於多字節的編碼單元,需要有一個標記顯式的告訴計算機,按照什麼樣的順序解析字符,也就是字節序,字節序分爲大端字節序和小端字節序 小端字節序簡寫爲LE(Little-Endian),表示低位字節在前,高位字節在後,高位字節保存在內存的高地址端,而低位字節保存在內存的低地址端 大端字節序簡寫爲BE(Big-Endian),表示高位字節在前,低位字節在後,高位字節保存在內存的低地址端,低位字節保存在在內存的高地址端 下面以 0x4E2D 爲例來說明大端和小端,具體參見下圖: 數據是從高位字節到低位字節顯示的,這也更符合人們閱讀數據的習慣,而內存地址是從低地址向高地址增加 所以,字符0x4E2D 數據的高位字節是 4E,低位字節是 2D 按照大端字節序的高位字節保存內存低地址端的規則,4E 保存到低內存地址 0x10001 上,2D 則保存到高內存地址 0x10002 上 對於小端字節序,則正好相反,數據的高位字節保存到內存的高地址端,低位字節保存到內存低地址端的,所以 4E 保存到高內存地址 0x10002 上,2D 則保存到低內存地址 0x10001 上 BOM BOM是byte-ordermark的縮寫,是"字節序標記"的意思,它常被用來當做標識文件是以UTF-8、UTF-16或UTF-32編碼的標記 在Unicode編碼中有一個叫做"零寬度非換行空格"的字符(ZEROWIDTHNO-BREAKSPACE),用字符 FEFF 來表示 對於UTF-16,如果接收到以 FEFF 開頭的字節流,就表明是大端字節序,如果接收到 FFFE,就表明字節流是小端字節序 UTF-8沒有字節序問題,上述字符只是用來標識它是UTF-8文件,而不是用來說明字節順序的。

"零寬度非換行空格"字符的UTF-8編碼是 EFBBBF,所以如果接收到以 EFBBBF 開頭的字節流,就知道這是UTF-8文件 下面的表格列出了不同UTF格式的固定文件頭 qnNBiV 根據上面的固定文件頭,下面列出了 "中" 字在文件中的存儲(包含文件頭) HbP4LN 常見的字符編碼的問題 Redis中文key的顯示 有時候我們需要向redis中寫入含有中文的數據,然後在查看數據,但是會看到一些其他的字符,而不是我們寫入的中文 上圖中,我們向redis寫入了一個"中"字,通過get命令查看的時候無法顯示我們寫入的"中"字 這時候加一個--raw參數,重新啓動redis-cli即可,也即執行redis-cli--raw命令啓動redis客戶端,具體的如下圖所示 MySQL中的utf8和utf8mb4 MySQL中的"utf8"實際上不是真正的UTF-8,"utf8"只支持每個字符最多3個字節,對於超過3個字節的字符就會出錯,而真正的UTF-8至少要支持4個字節 MySQL中的"utf8mb4"纔是真正的UTF-8 下面以test表爲例來說明,表結構如下: mysql>showcreatetabletest\G ***************************1.row*************************** Table:test CreateTable:CREATETABLE`test`( `name`char(32)NOTNULL )ENGINE=InnoDBDEFAULTCHARSET=utf8 1rowinset(0.00sec) 向 test 表分別插入 "中" 字和Unicode碼爲 0x10A6F 的字符,這個字符需要從 https://unicode-table.com/cn/10A6F/ 直接複製到MySQL控制檯上,手工輸入會無效,具體的執行結果如下圖: 從上圖可以看出,插入 "中" 字成功,插入 0x10A6F 字符失敗,錯誤提示無效的字符串,\xF0\X90\XA9\xAF 正是 0x10A6F 字符的UTF-8編碼,佔用 4 個字節,因爲MySQL的utf8編碼最多隻支持 3 個字節,所以插入會失敗 把 test 表的字符集改成 utf8mb4 ,排序規則改成 utf8bm4_unicode_ci,具體如下圖所示: 字符集和排序方式修改之後,再次插入 0x10A6F 字符,結果是成功的,具體執行結果如下圖所示 上圖中,setnamesutf8mb4 是爲了測試方便,臨時修改當前會話的字符集,以便保持和服務器一致,實際解決這個問題需要修改 my.cnf 配置中服務器和客戶端的字符集 小結 本文從字符編碼的歷史介紹了Unicode出現的原因,接着介紹了Unicode字符集中三種不同的編碼方式:UTF-8、UTF-16、UTF-32以及它們的的編碼方法,緊接着介紹了字節序、BOM,最後講到了字符集在MySQL和Redis應用中常見的問題以及解決方案,更多關於Unicode的介紹請參考Unicode的RFC文檔 本文由Readfog進行AMP轉碼,版權歸原作者所有。

來源:https://mp.weixin.qq.com/s/hJliPe60pzImRDVQbiAaDw 2021年06月09日 #編碼 #字節 #節序 #進制 #字符 猜你喜歡 Golang字符的Unicode與UTF-8 Go字符串編碼?UTF-8?Unicode?看完就通! 數據庫索引,終於懂了 每個JavaScript開發者都應該瞭解的Unicode 【漫畫】什麼是字符集和編碼?ASCII、UTF-8、UTF-16、UTF-32又是什麼? UnixIPC之消息隊列 流程即代碼:雲研發、低代碼IDE——Uncode VSCode語言插件開發入門 vscode插件原理淺析與實戰 codereview流程規範 CleanCode系列之異常處理 60個神級VSCode插件!



請為這篇文章評分?