1. ASCII
??我們知道,計算機內部是通過二進制數據進行操作的,所有的信息最終都會轉換為一個二進制值,二進制只有0和1兩種狀態,因此8個二進制位就可以組合出256種狀態,而8個二進制位被稱為一個字節(byte),也就是說一個字節可以用來表示256種狀態,狀態的范圍是0000 0000
到1111 1111
。
??最開始計算機剛出現的時候,計算機的發源地,也就是美國的一群人,通過編碼的方式將所有的英文及相應的標點符號,空格,數字等,編到了1個字節的后7位上,也就是一個字節的0-127種狀態,范圍是
0000 0000
到0111 1111
,這樣當計算機讀到對應的二進制值時,就可以解析為相應的符號,然后進行后續的操作,而這種編碼方式被稱為ASCII
編碼,所以ASCII碼的編碼形式也就是0 XXX XXXX
。
??該字節的第一位有時候也稱為偶校驗位,加上二進制編碼的剩余7位,恰好是一個字節。ASCII碼規定8個二進制位的最高一位是0,剩下的7位可以給出128個編碼,表示128個不同的字符。其中95個編碼,對應著計算機終端能敲入并且可以顯示的95個字符,比如大小寫的26個英文字母,數字和標點符號等;另外的33個字符,編碼值為0~31和127,他們被用作控制碼,控制計算機設備和軟件的一些運行情況。
一開始,計算機只在美國使用,所以大家都使用ASCII碼來處理英文沒什么問題,后來,隨著計算機的普及,越來越多的國家都開始使用計算機,而每個國家的語言并不是相同的,這就產生了一個問題,對一些國家來說,常規的ASCII碼所表示的128個字符是不夠用的,所以就引申出了ISO-8859-1,GB2312等編碼。
2. ISO-8859-1
其中,上述ASCII碼滿足不了的國家就包括歐洲的一些國家,于是這些歐洲國家就決定:
- 將自己使用的語言給編碼到原先一個字節中閑置的第一位上,范圍也就是從
1000 0000
到1111 1111
,這樣的話,這一套編碼系統就可以表示256個符號了,其中0到127和ASCII碼表示的符號是相同的,而128到255則分別表示自己國家所使用的符號。
- 這其中,就包括了西歐國家的
ISO-8859-1
,中歐的ISO-8859-2
,南歐的ISO-8859-3
,北歐的ISO-8859-4
以及相應的阿拉伯,希臘等相關國家的ISO-8859-X
系列,其中,ISO-8859-1
又名Latin-1
,而ISO-8859-2
又名Latin-2
等,而有關相應的ISO-8859系列的詳細說明可參考維基百科:維基百科-ISO/IEC 8859 詳細介紹
3. GB2312
??我們把視線轉回國內,在20世紀后期計算機在國內開始普及的時候,同樣也遇到了相同的問題,并且由于中華文化博大精深,漢字的發展源遠流長,光常用的漢字就有幾千多個,很顯然即使把ASCII碼的第一位也用上,也保存不了多少個漢字。所以呢,國內的開發者就整出了一套新的編碼方式,不過我們先來看下區位碼。
3.1 區位碼
??GB2312編碼中對漢字進行了“分區”處理,編號為01區至94區;每區含有94個字符,編號為01位至94位。這種表示方式也稱為區位碼,每一個字符都由與其唯一對應的區號和位號所確定,區位碼一般用十進制來表示,其中:
- 01–09區為符號,數字;
- 16–55區為一級漢字,約3755個,按漢語拼音字母/筆形順序排列;
- 56–87區為二級漢字,約3008個,按部首/筆畫排序排列;
- 10-15區和88-94區 未使用空白區;
舉例來說,“啊”字是第16區中的第1個字符,它的區位碼就是1601;“琛”字是第72區的第41個字符,它的區位碼就是7241。然后我們再來看一下具體的編碼方式。
3.2 GB2312編碼
- 首先,一個字節1到127內的符號不變,和ASCII碼保持一致;然后再使用一個字節和前面的那個字節進行連接,通過兩個字節來表示一個漢字,其中第一個字節稱為高位字節,第二個字節稱為低位字節;
- 高位字節= 區號 + 0xA0
- 低位字節= 位號 + 0xA0
舉例:“啊”的區位碼是1601,
高位字節 = 0x10(=16) + 0xA0 = 0xB0
低位字節 = 0x01(=1) + 0xA0 = 0xA1
所以,“啊”的編碼就是0xB0(第一個編碼單元)0xA1(第二個編碼單元),所以最終編碼就是B0A1。
不過需要簡單說明的是:
- 高位字節編碼從0xA1到 0xF7(把01–87區的區號加上0xA0),低位字節編碼從0xA1到0xFE(把01–94加上0xA0),這樣我們就可以組合出大約7000多個簡體漢字了。
- 在這些編碼里,我們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 里本來就有的數字、標點、字母都統統重新編了兩個字節長的編碼,這就是常說的”全角”字符,而原來在127號以下的那些就叫”半角”字符了。
- 編碼完成之后,不管是高位字節還是低位字節,它們的最高位都是1,這樣就可以和ASCII字符的編碼區分開,所以說,GB2312是對ASCII碼的中文擴展。
另外,區位碼這個東西可以理解為一種字符集,而GB2312則可以算作支持該字符集的編碼,這樣的話其實有點類似于我們后面要說的Unicode和UTF-8的關系。
4. GBK,Big5,GB18030
??GB2312雖然收錄了常用的簡體中文,但由于漢字實在太多,還包括一些不常用的生僻字及繁體中文,所以GBK就誕生了。GBK是在GB2312的基礎上,將生僻字,繁體中文,還有日韓漢字,以及GB2312中不包含的漢字部首符號、豎排標點符號等都收錄進去,組成了GBK編碼。
- GBK總體編碼范圍是0x8140到0xFEFE,高位字節范圍是0x81-0xFE,低位字節范圍是0x40-7E和0x80-0xFE,其中低位字節不包括0x7F的組合。
Big5:至于Big5,算是和GB2312同一時期的,是臺灣,香港等地流行的主要基于繁體中文的雙字節字符集,同樣使用了兩個字節,第一個字節稱為高位字節,第二個字節稱為低位字節,高位字節的范圍在0x81-0xFE,低位字節的范圍在0x40-0x7E以及0xA1-0xFE。要了解更多Big5的,可參考:維基百科-大五碼(Big5)
GB18030:而GB18030,則是在GBK的基礎上,新收錄了少數民族的字符,及GBK不支持的字符。GBK和GB2312都是雙字節編碼,而GB18030則是變長字節編碼,變長字節編碼,包含了單字節、雙字節和四字節三種方式:
GB18030的單字節編碼范圍是0x00-0x7F,與ASCII碼完全一致;而雙字節編碼的范圍和GBK相同,高字節是0x81-0xFE,低字節的編碼范圍是0x40-0x7E和0x80-0xFE;四字節編碼中第一、三字節的編碼范圍是0x81-0xFE,第二、四字節的范圍是0x30-0x39。
5. Unicode
??由于當時各個國家都像中國這樣搞出一套自己的編碼標準,結果互相之間誰也不懂誰的編碼,誰也不支持別人的編碼。當時的中國人想讓電腦顯示漢字,就必須裝上一個”漢字系統”,專門用來處理漢字的顯示、輸入的問題,裝錯了字符系統,顯示就會亂了套。這怎么辦?就在這時,一個叫 ISO (國際標誰化組織)的國際組織決定著手解決這個問題。他們采用的方法很簡單:廢了所有的地區性編碼方案,重新搞一個包括了地球上所有文化、所有字母和符號的編碼!他們打算叫它“Universal Multiple-Octet Coded Character Set”,簡稱 UCS, 俗稱 UNICODE
。
- Unicode 開始制訂時,計算機的存儲器容量極大地發展了,空間再也不成為問題了。于是 ISO 就直接規定必須用兩個字節,也就是16位來統一表示所有的字符,對于 ASCII 里的那些”半角”字符,UNICODE 包持其原編碼不變,只是將其長度由原來的8位擴展為16位,而其他文化和語言的字符則全部重新統一編碼。由于”半角”英文符號只需要用到低8位,所以其高 8位永遠是0,因此這種大氣的方案在保存英文文本時會多浪費一倍的空間。
- 在表示一個Unicode的字符時,通常會用“U+”然后緊接著一組十六進制的數字來表示這一個字符。Unicode的編碼范圍從U+0000到U+10FFFF,共有1,112,064個碼位(code point)可用來映射字符,Unicode的編碼空間可以劃分為17個平面(plane),每個平面包含216(65,536)個碼位。
- 17個平面的碼位可表示為從U+xx0000到U+xxFFFF,其中xx表示十六進制值從00到10,共計17個平面。第一個平面稱為基本多語言平面(Basic Multilingual Plane, BMP),或稱第零平面(Plane 0)。其他平面稱為輔助平面(Supplementary Planes)。基本多語言平面內,從U+D800到U+DFFF之間的碼位區塊是永久保留不映射到Unicode字符。
Unicode的編碼方式是UCS-2,也就是雙字節的編碼方式,針對的是基本多語言平面(BMP),而相應的輔助平面,則是對應UCS-4。
6. UTF系列
- Unicode雖然能覆蓋世界上所有的符號,但由于Unicode 在制訂時沒有考慮與任何一種現有的編碼方案保持兼容,這使得Unicode和另一種編碼進行轉換時,沒有什么直接的方式,必須通過查表來進行。所以Unicode 在很長一段時間內都沒有得到很大的推廣普及。并且由于計算機網絡的興起,Unicode如何在網絡上傳輸也是一個很重要的問題。
這時候,就出現了面向傳輸的Unicode的實現方式。一個字符的Unicode編碼是確定的。但是在實際傳輸過程中,由于不同系統平臺的設計不一定一致,以及出于節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF),其中UTF家族比較出名的就是UTF-8,UTF-16,UTF-32等。
6.1 UTF-8
??UTF-8,是一種變長的編碼方式,它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。
比如說如果一個僅包含基本7位 ASCII 字符的Unicode符號,如果每個字符都使用2字節的原Unicode編碼傳輸,其第一字節的8位始終為0,這就造成了比較大的浪費。對于這種情況,就可以使用UTF-8編碼,因為這是一種變長編碼,它將基本7位ASCII字符仍用7位編碼表示,占用一個字節(首位補0)。
UTF-8的編碼方式比較簡單:
- 對于單字節的符號,字節的第一位設為0,后面7位為這個符號的 Unicode 碼。因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的;
- 對于n字節的符號(n > 1),第一個字節的前n位都設為1,第n + 1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼;
編碼規則如下(上面是16進制范圍,下面是二進制范圍):
Unicode符號范圍(16進制) | UTF-8編碼方式(二進制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
Unicode符號范圍(二進制) | UTF-8編碼方式(二進制) |
---|---|
00000000-01111111 | 0xxxxxxx |
00000000 10000000-00000111 11111111 | 110xxxxx 10xxxxxx |
00001000 00000000-11111111 11111111 | 1110xxxx 10xxxxxx 10xxxxxx |
00000001 00000000 00000000 - 00010000 11111111 11111111 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
根據上表,解讀 UTF-8 編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符占用多少個字節,其中中文一般都是在三個字節的范圍內。
例如”嚴”字的Unicode編碼是\u4E25。0x4E25在0x0800~0xFFFF之間,所以使用第三行模板 1110xxxx 10xxxxxx 10xxxxxx。將4E25寫成二進制是:0100 1110 0010 0101,然后,從嚴的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0,得到 11100100 10111000 10100101,也就是 0xE4B8A5 ,這就是嚴字的UTF8編碼。
參考:維基百科-UTF-8
6.2 UTF-16,UTF-32
??UTF-16也是一種變長的編碼方式,它可以使用兩個字節或四個字節來表示一個符號。其實UTF-16與Unicode的表示方式UCS-2是對應的,在沒有輔助平面字符(surrogate code points)前,UTF-16與UCS-2所指的是同一的意思。但當引入輔助平面字符后,就稱為UTF-16了。
不過UTF-16在表示BMP字符的時候,有一個問題:
- 在Mac機和個人PC上,對字節順序的理解是不一致的。這時同一字節流可能會被解釋為不同內容,如某字符為十六進制編碼4E59,按兩個字節拆分為4E和59,在Mac上讀取時是從低字節開始,那么在Mac OS會認為此4E59編碼為594E,找到的字符為“奎”,而在Windows上從高字節開始讀取,則編碼為U+4E59的字符為“乙”。就是說在Windows下以UTF-16編碼保存一個字符“乙”,在Mac OS環境下打開會顯示成“奎”;
- 此類情況說明UTF-16的編碼順序若不加以人為定義就可能發生混淆,于是在UTF-16編碼實現方式中使用了大端序(
Big-Endian
,簡寫為UTF-16 BE
)、小端序(Little-Endian
,簡寫為UTF-16 LE
)的概念,以及可附加的字節順序記號(BOM)解決方案,目前在PC機上的Windows系統和Linux系統對于UTF-16編碼默認使用UTF-16 LE。
至于Big-Endian,是第一個字節在前,而Little-Endian,則是第二個字節在前。
而所謂的字節順序記號,在UTF-16表示為在文件或字符串流的最前面加入一個表示編碼順序的字符,這個字符的名字叫做"零寬度非換行空格"(zero width no-break space),用0xFE 0xFF表示。這正好是兩個字節,而且FF比FE大1。
- 如果一個文本文件的頭兩個字節是FE FF,就表示該文件采用大端序方式;
- 如果頭兩個字節是FF FE,就表示該文件采用小端序方式。
至于UTF-32的編碼,則對應于USC-4,也就是4個字節。與其他UTF相比,UTF-32的編碼長度是固定的,UTF-32中的每個32位值代表一個Unicode碼位,并且與該碼位的數值完全一致。
UTF-32的主要優點是可以直接由Unicode碼位來索引,相比之下,其他可變長度編碼需要進行"循序訪問"操作才能在編碼序列中找到第N個編碼;而UTF-32的主要缺點是每個碼位使用四個字節,空間浪費較多。
這里參考自:
維基百科-字節順序標記
維基百科-UTF-16介紹
維基百科-Unicode介紹
7. emoji表情問題
??該問題是說Emoji表情在保存到Mysql的時候保存失敗,提示亂碼。
??這里要簡單說下Emoji表情了,Emoji表情是一種特殊的字符,而不是像QQ表情一樣的普通字符的轉義表示。在Unicode編碼中,占用了U+1F300到U+1F64F中的部分范圍:
- Emoji字符的特殊之處在于,其使用的Unicode字符超出了通常使用的三字節UTF-8編碼的Unicode范圍,即BMP范圍U+0000到U+FFFF。按照UTF-8編碼規范,Emoji字符屬于輔助平面范圍,通常對應4字節的UTF-8編碼;
- 而Mysql的UTF-8最多只支持3個字節來表示一個符號,表示的只是17個平面中的基本多語言面 (Basic Multilingual Plane,BMP)字符,所以保存不了Emoji表情;
所以,這里就引申出了Mysql的utf8mb4字符集(其實就是4字節 UTF-8 Unicode 編碼),utf8mb4 字符集使用最多四個字節擴展UTF-8:
- 對于 BMP字符 UTF8 和 utf8mb4 存儲時是完全一樣的;
- 對于其他平面的字符,utf8mb4通過使用四個字節來存儲,并且utf8mb4是完全兼容UTF-8,因此從舊版本的MySQL UTF-8 升級數據時 不用擔心字符轉換或丟失數據;
不過需要注意的是,utf8mb4是mysql在5.5版本之后引入的字符集,并不是諸如UTF-8,UTF-16等通用的字符集。
8. 總結
- 可以這么理解,Unicode是字符集,ASCII、GB2312、GBK、GB18030既是字符集也是編碼方式,UTF-8只是編碼方式;
- Unicode從開始到現在一直在不斷增修,每個新版本都加入更多新的字符。目前最新的版本為2018年6月5日公布的11.0.0,已經收錄超過13萬個字符。Unicode涵蓋的數據除了視覺上的字形、編碼方法、標準的字符編碼外,還包含了字符特性,如大小寫字母。
- 一開始,Unicode的2 字節形式通常稱作 UCS-2,然而,由于2 字節數量的限制,UCS-2 只能表示最多 65536 個字符,所以后來有了4字節,Unicode 的 4 字節形式被稱為 UCS-4 或 UTF-32,能夠定義 Unicode 的全部擴展,最多可定義 100 萬個以上唯一字符。UCS 只是規定了如何編碼,并沒有規定如何傳輸、保存編碼,所以有些人說 Unicode編碼占用兩個字節,這種說法是不準確的。
本文參考自:維基百科
潛行者m-網頁編碼就是那點事
阮一峰-字符編碼筆記:ASCII,Unicode 和 UTF-8
UTF-16與UCS-2的區別
IBM developerWorks-深入分析 Java 中的中文編碼問題
《Java核心技術I》