字符串,那些你不知道的事
?
Everything you thought you knew about strings is wrong.
也許你會詫異,字符串有什么難的,即便遇到亂碼的情況隨便 Google 下就能找到解決方法,但是這樣你不覺得有種被動的感覺嘛,我覺得和學習任何東西一樣,學習編程首要是學習其思想,知道某事物為什么(why)要這么做,至于如何做(how)那只是前人提出的解決方案,我們可以參考,順便掌握下來。
本文下面首先講解字符、字符串、編碼、ASCII、Unicode、UTF-8 等一些基本概念,然后會簡單介紹在使用計算機時是如何與編碼打交道的。 希望大家在閱讀完本文后,都能對 string 有一全新的認識。
為什么需要字符編碼
當我們談到字符串(string或text)時,你可能會想到“計算機屏幕上的那些字符(characters)與符號(symbols)”,你正在閱讀的文章,無非也是由一串字符組成的。但是你也許會發現,你無法給“字符串”一明確定義,但是我們就是知道,就像給你一個蘋果,你能說出其名字,但是不能給出準確定義一樣。這個問題先放一放,后面我再解釋。
我們知道,計算機并不能直接處理操作字符與符號,它只認識 0、1 這兩個數字,所以如果想讓計算機顯示各種各樣的字符與符號,就必須定義它們與數字的一一映射關系,也就是我們所熟知的字符編碼(character encoding)。你可簡單的認為,字符編碼為計算機屏幕上顯示的字符與這些字符保存在內存或磁盤中的形式提供了一種映射關系。字符編碼紛繁復雜,有些專門為特定語言優化,像針對簡體中文的編碼就有?EUC-CN,HZ;針對日文的EUC-JP,針對英文的?ASCII;另一些專門用于多語言環境,像后面要講到的?UTF-8。
我們可以把字符編碼看作一種解密密鑰(decryption key),當我們收到一段字節流時,無論來自文件還是網絡,如果我們知道它是“文本(text)”,那么我們就需要知道采用何種字符編碼來解碼這些字節流,否則,我們得到的只是一堆無意義的符號,像 ������。
單字節編碼
計算機最早起源于以英文為母語的美國,英文中的符號比較少,用七個二進制位就足以表示,現在最常見也是最流行的莫過于 ASCII 編碼,該編碼使用 0 到 127 之間的數字來存儲字符(65表示“A”,97表示“a”)。
我們知道一個字節是 8 位,ASCII 編碼其實只使用了其中的低 7 位,還剩下 1 位。很多人就想著可以利用這最高的一位來表示更多的可見字符,由于 IBM 是那時最有名的 OEM,其制定的編碼規則影響范圍也最廣。
隨著時間的推進,計算機的使用范圍擴展到西方歐洲國家,像法國??、西班牙??,德國??等,它們這些國家的字母比英文要多。所以為了表示這些國家的語言,也需要借助最高位擴展 ASCII 編碼。但由于沒有統一的標準,有些地方用 130 表示?é,有些地方表示為 Hebrew letter Gimel??。在這些語言中,使用最廣的是?CP-1252 編碼,也稱為 Windows-1252 編碼,因為在?Windows 在 1.0 時就使用了該編碼,隨著 Windows 的普及,大家就沿用了 Windows-1252 的說法。
ISO-8859-1
既然說到了 Windows-1252,那么就不能不提它與 ISO-8859-1 的關系,我相信大家對 ISO-8859-1 這個編碼肯定很熟悉,我還記得第一次用 Dreamweaver 寫 HTML 時,其 HTML 模板中默認編碼就是 ISO-8859-1,還有一個比較常見的場景是在 mysql 中,mysql 的默認編碼為 latin-1,這其實是 ISO-8859-1 的一個別名而已。
ISO-8859-1?是早期 8 位編碼方案的一種,是?ISO/IEC 8859?編碼系列的一種, 第一版發布于 1987 年。它主要是針對西歐國家設計,現在大多數流行的 8 位編碼方案都以它為基礎,這其中就包括 Windows-1252。
Windows-1252 與 ISO-8859-1 的主要不同在于 0x80 到 0x9F 之間的字符,在 ISO-8859-1 中為控制字符(control characters),在 Windows-1252 中為可打印字符(printable characters)。
在 HTTP 協議中,以text/開頭的 MIME 類型的文件默認編碼為 ISO-8859-1,但是由于控制字符在文件中用處不大,所以大多數客戶端程序用 Windows-1252 來解碼,這也就是它們之間經?;煜闹饕?。
字符集 vs 字符編碼
在介紹完 ASCII 之后,需要強調一個很重要但被大多數人都忽略的一個概念問題。
我們平時說的 ASCII 其實有兩個含義,一個是 ASCII 字符集,另一個是 ASCII 編碼。
ASCII 字符集只是定義了字符與字符碼(character code,也稱 code point 代碼點)的對應關系。也就是說這一層面只是規定了字符A用 65 表示,至于這個 65 在內存或硬盤中怎么表示,它不管,那是 ASCII 編碼做的事。
ASCII 編碼規定了用 7 個二進制位來保存 ASCII 字符碼,即定義了字符集的存儲形式。
說到這里你也許會問,那既然用 7 個二進制位就能夠表示所有 ASCII 字符碼了,為什么現在一個字節是 8 位,而不是 7 位呢,這不是浪費嗎?
其實這是早期設計者有意而為之:
7 位表示 ASCII 字符碼,剩下 1 位為?Parity bit,也稱為校驗位,用以檢查數據的正確性。
關于數據校驗,我這里不打算展開講,感興趣的可以參考?Error detection and correction。
為了讓大家更清楚的明白這兩者以及相關概念的關系,我畫了圖,便于大家理解。
- Character 字符。即我們看到的單個符號,像“A”、“啊”等
- Code point 代碼點。一個無符號數字,通常用16進制表示。代碼點與字符的一一對應關系稱為字符集(Character Set),這種對應關系肯定不止一種,也就導致了不同字符集的出現,像 ASCII、ISO-8859-1、GB2312、GBK、Unicode 等。
- Bytes 二進制字節。其含義為代碼點在內存或磁盤中的表示形式。代碼點與二進制字節的一一對應關系稱為編碼(Encoding),當然這種對應關系也不是唯一的,所以編碼也有很多種,像 ASCII、ISO-8859-1、ENC-CN、GBK、UTF-8等。
上面這個圖基本把我們平時經常混淆的概念清晰地區分開了,大家一定要充分理解并牢記于心。
多字節編碼
多字節編碼主要用于我們亞洲國家,像中文(Chinese),日文(Japanese),韓文(Korean)(業界一般稱為 CJK)等象形(表意)文字(ideograph-based language),字符數量比較多,1 個字節是放不下的,所以需要更多的字節來進行字符的編碼。
ISO/IEC 2022?標準為多字節編碼制定了一套標準,主要有下面兩個方面:
為了能夠表示多種字符集 ISO/IEC 2022 引入了escape sequences,也就是我們中文里面說的“轉義字符”的意思;為了能夠兼容之前的 7 位編碼系統,像 ASCII,實現 ISO/IEC 2022 標準的編碼系統一般都是變長編碼。
GB2312
GB2312?是我國國家標準總局在1980年發布一套遵循 ISO/IEC 2022 標準的字符集,在 GB2312 中,字符碼一般稱為區位碼,由于該字符集需要兼容 ASCII 字符集,所以它只能一個字節中的 7 位,剩下 1 位用于區分,比如可以通過最高位為 1 表示 GB2312 字符集,為 0 表示 ASCII 字符集。
GB2312 使用兩個字節來表示字符碼,最多可以表示 94 * 94 個字符,但是 GB2312 并沒有全部使用,留了一部分方便后面擴展用。
實現 GB2312 字符集的編碼主要是?EUC-CN,該編碼與 ASCII 編碼兼容。
我們平時說的 GB2312 編碼其實就是指的 EUC-CN 編碼,這一點需要明白。
GBK
GBK?字符集是對 GB2312 字符集的擴展,GBK 并沒有一個官方標準,現在使用最廣的標準是微軟在 Windows 95 中實現的版本——CP936?編碼。
GBK 也使用兩個字節來表示字符碼,94 * 94 的區位碼分布可以參考下面這個圖,截自?Wikipedia
同 GB2312 一樣,我們平時說的 GBK 編碼其實就是指的 CP936 編碼。
除了我們中國的 GB* 系統字符集以外,日本、韓國各有各的字符集標準,雖然都是基于 ISO/IEC 2022,但是具體的表示方式千差萬別,比如 1601 在 GB2312 中表示“啊”,但在日韓就不知道表示什么含義了。
所以,我們需要一種囊括世界上所有字符的一套字符集,在該字符集中,字符碼與字符一一對應,比如字符碼 0x41 表示英文字母A,在任何國家都表示A,即使該國家沒有這個字符。解決這個問題的是現在最流行的一套字符集——Unicode。
Unicode
Unicode 的全稱是 universal character encoding,中文一般翻譯為“統一碼、萬國碼、單一碼”。
在 Unicode 中字符碼稱為 code point,用 4 個字節來表示,這么做主要是為了涵蓋世界上所有的字符。寫法一般為U+XXXX,XXXX 為用 16 進制表示的數字。比如,U+0041表示A。
Unicode 的存儲形式
Unicode 的存儲形式一般稱為UTF-*編碼,其中 UTF 全稱為?Unicode Transformation Format,常見的有:
UTF-32
UTF-32?編碼是 Unicode 最直接的存儲方式,用 4 個字節來分別表示 code point 中的 4 個字節,也是?UTF-*編碼家族中唯一的一種定長編碼(fixed-length encoding)。UTF-32 的好處是能夠在O(1)時間內找到第 N 個字符,因為第 N 個字符的編碼的起點是 N*4 個字節,當然,劣勢更明顯,四個字節表示一個字符,別說以英文為母語的人不干,我們中國人也不干了。
UTF-16
UTF-16?最少可以采用 2 個字節表示 code point,需要注意的是,UTF-16 是一種變長編碼(variable-length encoding),只不過對于 65535 之內的 code point,采用 2 個字節表示而已。如果想要表示 65535 之上的字符,需要一些 hack 的手段,具體可以參考wiki UTF-16#U.2B10000toU.2B10FFFF。很明顯,UTF-16 比 UTF-32 節約一半的存儲空間,如果用不到 65535 之上的字符的話,也能夠在O(1)時間內找到第 N 個字符。
UTF-16 與 UTF-32 還有一個不明顯的缺點。我們知道不同的計算機存儲字節的順序是不一樣的,這也就意味著U+4E2D?在 UTF-16 可以保存為4E 2D,也可以保存成2D 4E,這取決于計算機是采用大端模式還是小端模式,UTF-32 的情況也類似。為了解決這個問題,引入了?BOM (Byte Order Mark),它是一特殊的不可見字符,位于文件的起始位置,標示該文件的字節序。對于 UTF-16 來說,BOM 為U+FEFF(FF 比 FE 大 1),如果 UTF-16 編碼的文件以FF FE開始,那么就意味著其字節序為小端模式,如果以FE FF開始,那么就是大端模式。 其他 UTF-* 編碼的 BOM 可以參考?Representations of byte order marks by encoding。
UTF-8
UTF-16 對于以英文為母語的人來說,還是有些浪費了,這時聰明的人們(準確說是Ken Thompson與Rob Pike)又發明了另一個編碼——UTF-8。在 UTF-8 中,ASCII 字符采用單字節。其實,UTF-8 前 128 個字符與 ASCII 字符編碼方式一致;擴展的拉丁字符像?、?等采用2個字節存儲;中文字符采用 3 個字符存儲,使用頻率極少字符采用 4 個字節存儲。由此可見,UTF-8 也是一種變長編碼(variable-length encoding)。
UTF-8 的編碼規則很簡單,只有二條:?
1. 對于單字節的符號,字節的第一位設為0,后面7位為這個符號的 code point。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。?
2. 對于n字節的符號,第一個字節的前n位都設為1,第n+1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的 code point。
通過上面這兩個規則,UTF-8 就不存在字節順序在大小端不同的情況,所以用 UTF-8 編碼的文件在任何計算機中保存的字節流都是一致的,這是其很重要一優勢;UTF-8 的另一大優勢在于對 ASCII 字符超節省空間,存儲擴展拉丁字符與 UTF-16 的情況一樣,存儲漢字字符比 UTF-32 更優。
UTF-8 的一劣勢是查找第 N 個字符時需要O(N)?的時間,也就是說,字符串越長,就需要更長的時間來查找其中的每個字符。其次是在對字節流解碼、字符編碼時,需要遵循上面兩條規則,比 UTF-16、UTF-32 略麻煩。
隨著互聯網的興起,UTF-8 是逐漸成為使用范圍最廣的編碼方案。下圖為 Google 在 2010 年初做的統計(鏈接需翻墻)
由于 Google 的爬蟲遍布全世界,這個數據可信度比較高。
UCS
我們在互聯網上查找編碼相關資料時,經常會看到UCS-2、UCS-4編碼,它們和UTF-*編碼家族是什么關系呢?要想理清它們之間的關系,需要先弄清楚,什么是?UCS。
UCS?全稱是?Universal Coded Character Set,是由?ISO/IEC 10646定義的一套標準字符集,是很多字符編碼的基礎,UCS 中大概包含 100,000 個抽象字符,每一個字符都有一唯一的數字編碼,稱為 code point。
在19世紀八十年代晚期,有兩個組織同時在 UCS 的基礎上開發一種與具體語言無關的統一的編碼方案,這兩個組織分別是?IEEE?與?Unicode Consortium,為了保持這兩個組織間編碼方案的兼容性,兩個組織嘗試著合作。早期的兩字節編碼方案叫做“Unicode”,后來改名為“UCS-2”,在研發過程發,發現 16 位根本不能夠囊括所有字符,于是 IEEE 引入了新的編碼方案——UCS-4 編碼,這種編碼每個字符需要 4 個字節,這一行為立刻被 Unicode Consortium 制止了,因為這種編碼太浪費空間了,又因為一些設備廠商已經對 2 字節編碼技術投入大量成本,所以在 1996 年 7 月發布的 Unicode 2.0 中提出了 UTF-16 來打破 UCS-2 與 UCS-4 之間的僵局,UTF-16 在 2000 年被?IEFE?組織制定為RFC 2781標準。
由此可見,UCS-*?編碼是一歷史產物,目前來說,統一編碼方案最終的贏家是?UTF-*?編碼。
實戰
操作系統
根據UTF-16 FOR PROCESSING,現在流行的三大操作系統 Windows、Mac、Linux 均采用 UTF-16 編碼方案,上面鏈接也指出,現代編程語言像 Java、ECMAScript、.Net 平臺上所有語言等在內部也都使用 UTF-16 來表示字符。
上圖為 Mac 系統中文件瀏覽器 Finder 的界面,其中所有的字符,在內存中都是以 UTF-16 的編碼方式存儲的。
你也許會問,為什么操作系統都這么偏愛 UTF-16,Stack Exchange 上面有一個精彩的回答,感興趣的可以去了解
- Should UTF-16 be considered harmful?
- UTF-8 Everywhere
Locale
為了適應多語言環境,Linux/Mac 系統通過?locale?來設置系統的語言環境,下面是我在 Mac 終端輸入locale得到的輸出
LANG="en_US.UTF-8" <==主語言的環境 LC_COLLATE="en_US.UTF-8" <==字串的比較排序等 LC_CTYPE="en_US.UTF-8" <==語言符號及其分類 LC_MESSAGES="en_US.UTF-8" <==信息顯示的內容,如功能表、錯誤信息等 LC_MONETARY="en_US.UTF-8" <==幣值格式的顯示等 LC_NUMERIC="en_US.UTF-8" <==數字系統的顯示信息 LC_TIME="en_US.UTF-8" <==時間系統的顯示資料 LC_ALL="en_US.UTF-8" <==語言環境的整體設定 12345678locale 按照所涉及到的文化傳統的各個方面分成12個大類,上面的輸出只顯示了其中的 6 類。為了設置方便,我們可以通過設置LC_ALL、LANG來改變這 12 個分類熟悉。其優先級關系為
LC_ALL?>?LC_*?>?LANG
設置好 locale,操作系統在進行文本字節流解析時,如果沒有明確制定其編碼,就用 locale 設定的編碼方案,當然現在的操作系統都比較聰明,在用默認編碼方案解碼不成功時,會嘗試其他編碼,現在比較成熟的編碼測探技術有Mozila 的 UniversalCharsetDetection?與?ICU 的 Character Set Detection?。
編程語言
Java
一般來說,高級編程語言都提供都對字符的支持,像 Java 中的?Character?類就采用 UTF-16 編碼方案。
這里有個文字游戲,一般我們說“某某字符串是XX編碼”,其實這是不合理的,因為字符串壓根就沒有編碼這一說法,只有字符才有,字符串只是字符的一串序列而已。 不過我們平時并沒有這么嚴謹,不過你要明白,當我們說“某某字符串是XX編碼”時,知道這其實指的是該字符串中字符的編碼就可以了。 這也就回答了本文一開始提到問題,什么是字符串:
Bytes are not character, bytes are bytes. Characters are an abstraction. A string is a sequence of those abstraction.
我們可以做個簡單的實驗來驗證 Java 中確實使用 UTF-16 編碼來存儲字符:
public class EncodingTest { public static void main(String[] args) { String s = "中國人a"; try { //線程睡眠,阻止線程退出 Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } } } 1234567891011在使用 javac 編譯這個類時,javac 會按照操作系統默認的編碼去解析字節流,如果你保存的源文件編碼與操作系統默認不一致,是可能出錯的,可以在啟動 javac 命令時,附加-encoding <encoding>選項來指明源代碼文件所使用的編碼。
12345678在 vim 中可以看到下圖所示片段
其中我用紅框標注部分就是上面 EncodingTest 類中字符串s的內容,4e2d是“中”的 code point,56fd是“國”的 code point,?4eba是“人”的 code point,0061是“a”的 code point。而在 UTF-16 編碼中,0-66535之間的字符直接用兩個字節存儲,這也就證明了 Java 中的?Character?是使用 UTF-16 編碼的。
Python
首先說下 Python 解釋器如何解析 Python 源程序。
在 Python 2 中,Python 解析器默認用 ASCII 編碼來讀取源程序,當程序中包含非 ASCII 字符時,解釋器會報錯,下面實驗在我 Mac 上用 python 2.7.6 進行:
$ cat str.py #!/usr/bin/env python a = "中國人"$ python str.pyFile "str.py", line 2 SyntaxError: Non-ASCII character '\xe4' in file str.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details 1234567我們可以通過在源程序起始處用coding: name或coding=name來聲明源程序所用的編碼。
Python 3 中改變了這一行為,解析器默認采用 UTF-8 解析源程序。
按理接下來應該介紹 Python 中對字符的處理了,但介于本文篇幅,這里不再介紹,后面會單獨寫篇文章進行介紹。感興趣的可以先參考下面的文章: -?More About Unicode in Python 2 and 3
JavaScript
- JavaScript’s internal character encoding: UCS-2 or UTF-16?
HTML/XML
在我們的 Web 瀏覽器接收到來自世界各地的 HTML/XML 時,也需要正確的編碼方案才能夠正常顯示網頁,在現代的 HTML5 頁面,一般通過下面的代碼指定
<meta charset="UTF-8"> 14.0.1 之前的 HTML 頁面使用下面的代碼
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> 1XML 使用屬性標注其編碼
1仔細想想,這里有個矛盾的地方,因為我們需要事先知道某字節流的編碼才能正確解析該字節流,而這個字節流的編碼是保存在這段字節流中的,和“雞生蛋,蛋生雞”的問題有點像,其實這并不是一個問題,因為大部分的編碼都是兼容 ASCII 編碼的,而這些 HTML/XML 開始處基本都是 ASCII 字符,所以采用瀏覽器默認的編碼方案即可解析出該字節流所聲明的編碼,在解析出該字節流所用編碼后,使用該編碼重新解析該字節流即可。
總結
通過上面的分析講解,我相信大部分人都會 string 產生了一新的認識。計算機中有很多我們認為理所當然的事,但事實一般并非如此,希望本文能對大家理解計算機有拋磚引玉的作用。
參考
?
Little endian 和 Big endian
上一節已經提到,UCS-2 格式可以存儲 Unicode 碼(碼點不超過0xFFFF)。以漢字嚴為例,Unicode 碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在后,這就是 Big endian 方式;25在前,4E在后,這是 Little endian 方式。
這兩個古怪的名稱來自英國作家斯威夫特的《格列佛游記》。在該書中,小人國里爆發了內戰,戰爭起因是人們爭論,吃雞蛋時究竟是從大頭(Big-endian)敲開還是從小頭(Little-endian)敲開。為了這件事情,前后爆發了六次戰爭,一個皇帝送了命,另一個皇帝丟了王位。
第一個字節在前,就是"大頭方式"(Big endian),第二個字節在前就是"小頭方式"(Little endian)。
那么很自然的,就會出現一個問題:計算機怎么知道某一個文件到底采用哪一種方式編碼?
Unicode 規范定義,每一個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫做"零寬度非換行空格"(zero width no-break space),用FEFF表示。這正好是兩個字節,而且FF比FE大1。
如果一個文本文件的頭兩個字節是FE FF,就表示該文件采用大頭方式;如果頭兩個字節是FF FE,就表示該文件采用小頭方式。
實例
下面,舉一個實例。
打開"記事本"程序notepad.exe,新建一個文本文件,內容就是一個嚴字,依次采用ANSI,Unicode,Unicode big endian和UTF-8編碼方式保存。
然后,用文本編輯軟件UltraEdit 中的"十六進制功能",觀察該文件的內部編碼方式。
1)ANSI:文件的編碼就是兩個字節D1 CF,這正是嚴的 GB2312 編碼,這也暗示 GB2312 是采用大頭方式存儲的。
2)Unicode:編碼是四個字節FF FE 25 4E,其中FF FE表明是小頭方式存儲,真正的編碼是4E25。
3)Unicode big endian:編碼是四個字節FE FF 4E 25,其中FE FF表明是大頭方式存儲。
4)UTF-8:編碼是六個字節EF BB BF E4 B8 A5,前三個字節EF BB BF表示這是UTF-8編碼,后三個E4B8A5就是嚴的具體編碼,它的存儲順序與編碼順序是一致的。
?
UTF的字節序和BOM
UTF-8以字節為編碼單元,沒有字節序的問題。UTF-16以兩個字節為編碼單元,在解釋一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16字節流“594E”,那么這是“奎”還是“乙”?
Unicode規范中推薦的標記字節順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:
在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規范建議我們在傳輸字節流前,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"。
這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。
UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。
Windows就是使用BOM來標記文本文件的編碼方式的。
?
?
?
String expressions have a repertoire attribute, which can have two values:ASCII: The expression can contain only characters in the Unicode range?U+0000?to?U+007F. UNICODE: The expression can contain characters in the Unicode range?U+0000?to?U+10FFFF. This includes characters in the Basic Multilingual Plane (BMP) range (U+0000?to?U+FFFF) and supplementary characters outside the BMP range (U+10000?to?U+10FFFF). 這里提到:Basic Multilingual Plane (BMP) 和 supplementary characters
? Basic Multilingual Plane (BMP):基本多文種平面
? Supplementary Multilingual Plane(SMP):多文種補充平面
? BMP就已經包含常用字符,而SMP只是一些不常用的字符,代碼點(字符)。如Emoji頭像的符號,撲克牌的符號等等。
? 關于BMP與SMP詳細可以查看wiki上的解釋:https://en.wikipedia.org/wiki/Plane_(Unicode)
? 系統默認設置元數據表的字符集為utf8,是通過參數character_set_system設置。character_set_results這個參數默認是utf8,當查詢表數據返回給客戶端,這個參數是控制返回的結構數據的字符集。如果希望服務器將元數據結果傳遞回不同的字符集,請使用SET NAMES語句強制服務器執行字符集轉換。客戶端程序可以在接收到來自服務器的結果后執行轉換??蛻舳藞绦修D換更為有效,但此選項并不總是適用于所有客戶端。
--------------------- utf8mb4與utf8(utf8mb3)轉換也是特別好轉換的:
1.utf8(utf8mb3)轉成utf8mb4可以存儲supplementary characters; 2.utf8(utf8mb3)轉成utf8mb4可能會增加數據存儲空間; 3.對于BMP character字符,utf8(utf8mb3)轉成utf8mb4相同的代碼值、相同的編碼、相同的長度,不會有變化。 4.對于supplementary character字符,utf8mb4會以4字節存儲,由于utf8mb3無法存儲supplementary character字符,因而在字符集轉換過程中,不用擔心字符無法轉換的問題。 5.表結構在轉換過程中需要調整:utf8(utf8mb3)字符集可變長度字符數據類型(VARCHAR和text類型)設定的表中列的字段長度,utf8mb4中將會存儲更少的字符。對于所有字符數據類型(CHAR、VARCHAR和文本類型),UTF8Mb4列最多可被索引的字符數比UTF8Mb3列要少。因此在轉換之前,要檢查字段類型。防止轉換后表,索引存儲的數據超出該字段定義長度,字段類型長度可以存儲的最大字節數。innodb索引列:最大索引列長度767 bytes,對于utf8mb3就是可以索引255個字符,對于utf8mb4就是可以索引191個字符。在轉換后不能滿足那么就需要換一個列來索引。以下是通過壓縮方式使索引更多的字節 Note: For InnoDB tables that use COMPRESSED or DYNAMIC row format, you can enable the innodb_large_prefix option to permit index key prefixes longer than 767 bytes (up to 3072 bytes). Creating such tables also requires the option values innodb_file_format=barracuda and innodb_file_per_table=true.) In this case, enabling the innodb_large_prefixoption enables you to index a maximum of 1024 or 768 characters for utf8mb3 or utf8mb4 columns, respectively. For related information, see Section 14.8.1.7, “Limits on InnoDB Tables”.
The preceding types of changes are most likely to be required only if you have very long columns or indexes. Otherwise, you should be able to convert your tables from utf8mb3 to utf8mb4 without problems, using ALTER TABLE as described previously.
6.應用于MySQL server 字符集也需要一一對應。 7.master 實例改變字符集,那么slave也需要相應的改變。
---------------------
轉載于:https://www.cnblogs.com/DataArt/p/10010693.html
總結
以上是生活随笔為你收集整理的字符串,那些你不知道的事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图片识别文字, OCR
- 下一篇: solr中文分词