程序員必備:徹底弄懂常見的7種中文字符編碼(轉載!)

看到很棒的文章,忍不住搬過來收藏!原作者是鵝廠大佬。我轉載一下哈
原文鏈接:程序員必備:徹底弄懂常見的7種中文字符編碼


程序開發常見的ASCII、GB2312、GBK、GB18030、UTF8、ANSI、Latin1中文編碼到底有何不同?如果你在業務中也曾經被亂碼搞暈過,不妨一起探究一下。

一、字符編碼要做什麼事情?

在計算機眼裏讀到的所有文字都是由0和1組成的字符串,爲了能讓漢字正常顯示在屏幕上,我們需要做以下兩件事情:

1、給所有的漢字一個獨一無二的數字編號,做一個數字編號到漢字的mapping關係(即字符集)
2、把這個數字編號能用0和1表示出來

這裏需要說明的是,第2件事情並不是直接把數字編號用二進制表示出來那麼簡單,還要處理多個字連在一起的時候如何做分隔的問題。

例如
如果我把”騰”編爲1號(二進制00000001,佔1byte),把“訊”編爲5號(二進制00000101,佔1byte),漢字這麼多,一定還有一個漢字被編爲了133號(二進制00000001 00000101,佔2bytes)。

那麼現在問題來了,當計算機讀到00000001 00000101這一串的時候,它應該顯示“騰訊”兩個字還是顯示那一個133號的文字?因此如何做分隔也是字符編碼需要考慮的事情。

第2件事情通常解決方案要麼就是規定好每個字長度(例如所有文字都是2bytes,不夠的前面用0補齊),要麼就是在用0和1表示的時候,不僅需要表示出數字編碼,還要暗示給計算機接下來多少個連續byte構成一個字,這個後面UTF8編碼中會提到。

我們通常所說的Unicode,其實只做了第1件事情,並且是給全世界所有語言的所有文字或字母一個獨一無二的數字編碼,這樣只要設計一種機制做第2件事情來表示Unicode,就可以顯示全球範圍內任何文字了。Unicode具體對所有語言的每個字母、文字的數字編號可以從其官方網站Unicode編碼表 查詢。該官網一大亮點是,中文編碼表的體量遠遠超過其他任何語言……

(爲了讓文章易懂,我暫時捨棄一些晦澀概念。晦澀地講,現代字符編碼模型其實分5個層次,可以參考鏈接瞭解:Unicode Technical Report #17 ,不在我們討論範圍內了)

二、幾種常見中文編碼的關係如何?

幾種常見中文編碼之間存在兼容性,一圖勝千言
在這裏插入圖片描述

所謂兼容性可以簡單理解爲子集,同時存在也不衝突,不會出現上文所說的不知道是“騰訊”還是133號文字的情況。

圖中我們可以看出,ASCII被所有編碼兼容,而最常見的UTF8與GBK之間除了ASCII部分之外沒有交集,這也是平時業務中最常見的導致亂碼場景,使用UTF8去讀取GBK編碼的文字,可能會看到各種亂碼。而GB系列的幾種編碼,GB18030兼容GBK,GBK又兼容GB2312,下文細講。

三、ASCII編碼

ASCII編碼每個字母或符號佔1byte(8bits),並且8bits的最高位是0,因此ASCII能編碼的字母和符號只有128個。有一些編碼把8bits最高位爲1的後128個值也編碼上,使得1byte可以表示256個值,但是這屬於擴展的ASCII,並非標準ASCII。通常所說的標準ASCII只有前128個值!

ASCII編碼幾乎被世界上所有編碼所兼容(UTF16和UTF32是個例外),因此如果一個文本文檔裏面的內容全都由ASCII裏面的字母或符號構成,那麼不管你如何展示該文檔的內容,都不可能出現亂碼的情況。
在這裏插入圖片描述

四、GB2312、GBK、GB18030編碼

GB全稱GuoBiao國標,GBK全稱GuoBiaoKuozhan國標擴展。GB18030編碼兼容GBK,GBK兼容GB2312,其實這三種編碼有着非常深厚的淵源,我們放在一起進行比較。

【GB2312】

最早一版的中文編碼,每個字佔據2bytes。由於要和ASCII兼容,那這2bytes最高位不可以爲0了(否則和ASCII會有衝突,也就只剩下一半可以編碼)。在GB2312中收錄了6763個漢字以及682個特殊符號,已經囊括了生活中最常用的所有漢字。(GB2312編碼全表:鏈接)

GB2312編碼表有個值得注意的點,這個表中也有一些數字和字母,與ASCII裏面的字母非常像。例如A3B2對應的是數字2(如下圖),但是ASCII裏面50(十進制)對應的也是數字2。他們的區別就是輸入法中所說的“半角”和“全角”。全角的數字2佔兩個字節。

通常,我們在打字或編程中都使用半角,即ASCII來編寫數字或英文字母。特別是編程中,如果寫全角的數字或字母,編譯器很有可能不認識……
在這裏插入圖片描述

【GBK】

由於GB2312只有6763個漢字,我漢語博大精深,只有6763個字怎麼夠?於是GBK中在保證不和GB2312、ASCII衝突(即兼容GB2312和ASCII)的前提下也用每個字佔據2bytes的方式又編碼了許多漢字。經過GBK編碼後,可以表示的漢字達到了20902個,另有984個漢語標點符號、部首等。值得注意的是這20902個漢字還包含了繁體字,但是該繁體字與臺灣Big5編碼不兼容,因爲同一個繁體字很可能在GBK和Big5中數字編碼是不一樣的。(GBK編碼全表:鏈接)

【GB18030】

然而,GBK的兩萬多字也已經無法滿足我們的需求了,還有更多可能你自己從來沒見過的漢字需要編碼。

這時候顯然只用2bytes表示一個字已經不夠用了(2bytes最多隻有65536種組合,然而爲了和ASCII兼容,最高位不能爲0就已經直接淘汰了一半的組合,只剩下3萬多種組合無法滿足全部漢字要求)。因此GB18030多出來的漢字使用4bytes編碼。當然,爲了兼容GBK,這個四字節的前兩位顯然不能與GBK衝突(實操中發現後兩位也並沒有和GBK衝突)。

我國在2000年和2005年分別頒佈的兩次GB18030編碼,其中2005年的是在2000年基礎上進一步補充。至此,GB18030編碼的中文文件已經有七萬多個漢字了,甚至包含了少數民族文字。有興趣的可以到國家標準委官網瞭解詳情,鏈接

GB2312,GBK,GB18030都是採取了固定長度的辦法來解決字符分隔(即前文所提的第2件事情)問題。GBK和GB2312比ASCII多出來的字都是2bytes,GB18030比GBK多出來的字都是4bytes。至於他們具體是如何做到兼容的,可以參考下圖:
在這裏插入圖片描述
這圖中展示了前文所述的幾種編碼在編碼完成後,前2個byte的值域(用16進製表示)。每個byte可以表示00到FF(即0至255)。ASCII編碼由於是單字節,所以沒有第2位。因爲GBK兼容GB2312,所以理論上上圖中GB2312的領土面積也可以算在GBK的範圍內,GB18030也同理。

上圖只是展示出了比之前編碼“多”出來的面積。GB18030由於是4bytes編碼,上圖只是展示了前2bytes的值域,雖然面積最小,但是如果後2bytes也算上,GB18030新編碼的字數實際上遠遠多於GBK。

可以看出爲了做到兼容性,以上所有編碼的前2bytes做到了相互值域不衝突,這樣就可以允許幾種不同編碼中的文字同時出現在同一個文本文件中。只要全都按照GB18030編碼的規則去解析並展示文件,就不會有亂碼出現。實際業務中GB18030很少提到,通常GBK見得比較多,這是因爲如果你去看一下GB18030裏面所編碼的文字,你會發現自己一個字也不認識……
在這裏插入圖片描述

五、UTF8編碼(Unicode Transformation Format)

99%的前端寫網頁時都會加上<meta charset="utf-8">,99%的後端工程師新建數據庫表時都會加上DEFAULT CHARSET=utf8(剩下的1%應該是忘了寫)。

之所以我們想讓UTF8一統天下,就是因爲UTF8可以表示出世界上所有的文字!UTF8與前面說的GB系列編碼不兼容,所以如果一個文件中即有UTF8編碼的文字,又有GB18030編碼的文字,那絕對會有亂碼。

Unicode賦予了全世界所有文字和符號一個獨一無二的數字編號,UTF8所做的事情就是把這個數字編號表示出來(即解決前文提到的第2件事情)。UTF8解決字符間分隔的方式是數二進制中最高位連續1的個數來決定這個字是幾字節編碼。0開頭的屬於單字節,和ASCII碼重合,做到了兼容。

在這裏插入圖片描述
以三字節爲例,開頭第一個字節的”1110”,有連續三個1,說明包括本字節在內,接下來三個字節一起構成了一個文字。凡是不屬於文字首字節的byte都以“10”開頭,上表中標註X的位置纔是真正用來表示Unicode數值的。

這種巧妙設計,把Unicode的數值和每個字的字節數融合在一起,最壞情況是6個字節表示一個字,已經足夠表示世界上所有語言的所有文字了。不過從這種表示方式也可以很顯然地看出來,UTF8和GBK沒有任何關係,除了都兼容ASCII以外。
在這裏插入圖片描述
舉例說明,中文“鵝”字,Unicode十進制值爲40517(16進製爲9E45,2進製爲1001 1110 0100 0101)。這個2進制值長度爲12位,查詢上面表格發現,二字節不夠表示,四字節太長,三字節剛好,因此可以表示爲 11101001 10111001 10000101,換算爲16進制即E9B985,這就是“鵝”字的UTF8編碼,佔3字節。另外,經查詢,“鵝”的GBK編碼爲B6EC,和UTF8的值完全不相干。

對於中文漢字來說,所有常用漢字的Unicode值都可以用3字節的UTF8表示出來,而GBK編碼的漢字基本是2字節(GB18030雖4字節但是日常沒人會寫那些字)。這也就導致了,如果把GBK編碼的中文文本另存爲UTF8編碼,體積會大50%左右。這也是UTF8的一點小瑕疵,存儲同樣的漢字,體積比GBK要大50%。

不過在“可表示世界上所有文字”這一巨大優勢面前,UTF8的這點小瑕疵可以忽略了,所以日常開發中最常使用UTF8

六、其他經常遇到的編碼

【ANSI編碼】

準確說,並不存在哪種具體的編碼方式叫做ANSI,它只是一個Windows操作系統上的別稱而已。在中文簡體Windows操作系統上,ANSI就是GBK;在泰語操作系統上,ANSI就是TIS-620(一種泰語編碼);在韓語操作系統上,ANSI就是EUC-KR(一種韓語編碼)。並且所謂的ANSI只存在於Windows操作系統上。

【Latin1編碼(又名ISO-8859-1編碼)】

相信99%的人第一次聽到Latin1都是在使用Mysql數據庫的時候接觸到的。Latin1是Mysql數據庫表的默認編碼方式。Latin1也是單字節編碼方式,也就是說最多隻能表示256個字母或符號,並且前128個和ASCII完全吻合。

Latin1在ASCII基礎上又充分利用了後面那128個值,賦予他們一些泰語、希臘語等字母或符號,將1個字節的256個值全部佔滿了。因爲項目中用不到,我對這種編碼的細節沒興趣瞭解,唯一感興趣的是爲什麼Mysql選它做默認編碼(爲什麼默認編碼不是UTF8)?以及如果忘了設置Mysql表的編碼方式時,用Latin1存儲中文會不會出問題?
在這裏插入圖片描述
爲什麼默認編碼是Latin1而不是UTF8?原因之一是Mysql最開始是某瑞典公司搞的項目,故默認collate都是latin1_swedish_ci。swedish可以理解爲其私心,不過latin1不管是否出於私心目的,單字節編碼作爲默認值肯定是比多字節做默認值更不容易在插入數據時報錯。

既然Latin1爲單字節編碼,並且將1個字節的所有256個值全部佔滿,因此理論上把任何編碼的值塞到Latin1字段都是可以存的(無非就是顯示亂碼而已)。

假設默認爲UTF8這一多字節編碼,在用戶誤把一個不使用UTF8編碼的字符串存進去時,很有可能因爲該字符串不符合UTF8的編碼要求導致Mysql根本沒法處理。這也是單字節編碼的一大好處:顯示可以亂碼,但是裏面的數據值永遠正確。

用Latin1存儲中文有沒有問題?答案是沒有問題,但是並不建議。例如你把UTF8編碼的“訊”字(UTF8編碼爲0xE8AEAF,佔三個字節)存入了Latin1編碼的Mysql表,那麼在Mysql眼裏,你存入的並不是一個“訊”字,而是三個Latin1的字母(0xE8,0xAE,0xAF)。本質上,你存的數據值依然是0xE8AEAF,這種“欺騙”Mysql的行爲並沒有導致數據丟失,只不過你需要注意讀取出來該值的時候,自己要以UTF8編碼的方式顯示出來,要不然就是亂碼。

因此,用Latin1存任何文字技術上都可以,但是經常會導致數據顯示亂碼。通常的解決方案,就是讓UTF8一統天下,建表的時候就聲明charset爲utf8。

文章最後,遺留一個問題。既然有這麼多編碼形式,如果給定一個文本文件,不告訴你是什麼編碼,如何用程序進行檢測?比較有代表性的編碼檢測庫爲python的chardet,其具體的原理,且待下回分解~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章