Unicode,GBK和UTF8

前言

其實這是個老生常談的問題了,相信你們在第一次遇到Unicode編碼問題時,都會在網上搜索一通,
找到幾個解釋,雖然有點雜亂,但仍是感受本身明白了些什麼,而後就繼續忙別的事情.
而我之因此就這個問題專門寫一篇文章,緣由是前兩天在與公司一位有十幾年工做經驗的JAVA程序員對接
API時, 我問他返回的漢字是什麼編碼的, 而他回答說"直接返回unicode". 一個如此有經驗的老程序員
對這種基本問題都不甚清楚, 所以我以爲仍是有必要好好說一下這個問題的.python

字符集

在介紹他們之間的區別時, 咱們先講下什麼是Unicode. 簡單來講,Unicode是一個字符集(character set),
和ASCII同樣, 其做用是用一系列數字來表示字符(character), 這些數字有時也稱爲碼點(code points).
在PC剛出來的時候,使用英文的幾位先驅認爲計算機須要表示的字符很少,26個英文字母加幾個回車換行等
特殊符號,總共一百個字符頂天了,因而就有了ASCII. ASCII碼的大小爲1個字節,定義了128個字符,
分別表示爲0-127. 好比字符'A'的碼點爲65,回車符'\n'的碼點爲10, 以下所示:程序員

>>> ord('A')
65
>>> ord('0')
48
>>> ord('\n')
10

固然, 後來人們發現, 世界上的字符遠遠不止128個, 所以就須要一個新的字符集能表示世上全部的字符,
包括一個英文字符,一個漢字字符,一個象形文字等. 這個字符集就是Unicode. Unicode前向兼容了ASCII,
最多能夠表示2^21(大概200萬)個字符,已經足夠囊括當今全部國家的文字, 以下所示:編碼

>>> u'ソ'
u'\u30bd'
>>> u'龍'
u'\u9f8d'
>>> u'A'
u'A'

目前unicode字符集表示完全部字符後還有剩餘, 這些暫時用不到的部分一般用佔位符FFFD表示.spa

字符編碼

有了字符集, 咱們如今能夠用任意數字來表示現實中的字符了. 但字符要保存在計算機中,必需要先通過編碼.
有人問, 數字直接保存在內存裏不就好了嗎? 可是用多少個字節表示一個數字,以及每一個字節的範圍這都是須要
預先約定的,這種約定就叫編碼. 假如咱們有四個數字,1,2,3,4要保存在計算機裏, 若是約定了utf-8編碼,
那麼在內存中的表示則以下:設計

00000001 00000010 00000011 00000100

其餘的編碼規則有utf-16,gb2312,gbk等,具體的編碼規則不在本文的範圍內,想要深刻了解的能夠在網上查閱相關的文檔.
所以,咱們能夠看到,若是不按照約定的規則來解碼,就頗有可能沒法還原出原來的數據,也就是咱們常常遇到的"亂碼".
下面以幾個例子來簡單說明:code

>>> u'你好'
u'\u4f60\u597d'
>>> u'你好'.encode('utf8')
'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> u'你好'.encode('gbk')
'\xc4\xe3\xba\xc3'
>>> u'你好'.encode('utf8').decode('gbk')
u'\u6d63\u72b2\u30bd'
>>> print u'你好'.encode('utf8').decode('gbk')
浣犲ソ

如上面的代碼所示, "你好"兩個漢字字符的unicode分別爲4f60和597d, utf-8編碼後佔6個字節, 而gbk編碼後佔4個字節.
若是用utf8編碼後錯誤地用gbk來解碼, 就會獲得3個unicode碼點,分別表示字符,;而若是用gbk編碼後
錯誤地用utf8來解碼, 則在解碼第二個字符時沒法湊夠3個字節, 所以會獲得未知的結果, 甚至會由於內存越界訪問引發程序異常.內存

注: 本文的python代碼示例是在Linux Terminal下運行的, 所以默認爲utf-8編碼, 若是你是在Windows cmd裏運行,
則一般默認GBK編碼, 所以亂碼會在不一樣地方出現:)utf-8

知道字符編解碼的用法以後,咱們就能夠解釋一下常見的一些亂碼由來了, 好比在Windows下,未初始化的棧會初始化爲0xcc,
未初始化的堆內存會初始化爲0xcd, 能夠看到前者爲'燙'的gbk編碼,然後者正好爲'屯'的gbk編碼, 以下所示:unicode

>>> u'燙'
u'\u70eb'
>>> u'燙'.encode('gbk')
'\xcc\xcc'
>>> u'屯'
u'\u5c6f'
>>> u'屯'.encode('gbk')
'\xcd\xcd'

前面也說過, unicode暫時沒用到碼點會用佔位符FFFD來表示, 若是這個佔位符被錯誤解析, 就會被看成有意義的內容了:文檔

>>> u'\uFFFD'.encode('utf8')
'\xef\xbf\xbd'
>>> u'錕斤拷'.encode('gbk')
'\xef\xbf\xbd\xef\xbf\xbd'
>>> print (u'\uFFFD'.encode('utf8')*2).decode('gbk')
錕斤拷

能夠看到,漢字"錕斤銬"(Unicode)的gbk編碼分別爲\xef\xbf, \xbd\xef和\xbf\xbd, 正好是unicode碼FFFD的utf8編碼
的疊加, 所以若是平時遇到多個utf8編碼的Unicode佔位符且不巧用了gbk的方式解碼,那就會看到熟悉的錕斤銬了.

其餘

在Windows的Notepad.exe中, 保存文件的格式能夠看到有以下幾種:

notepad

可剛剛不是說Unicode只是字符集嗎, 爲何上面顯示能夠保存爲Unicode"編碼"? 好吧, 其實這是Windows在命名上一個操蛋的
地方. 由於Windows內部使用UTF-16小端(UTF-16LE)做爲默認編碼,而且認爲這就是Unicode的標準編碼格式. 在Windows的世界中,
存在着ANSI字符串(在當前系統代碼頁中, 不可拓展),以及Unicode字符串(內部以UTF16-LE編碼保存). 所以notepad裏所說的
Unicode大端,其實就是UTF16-BE.

這其實也不怪Windows, 由於這是在Unicode出現的早期設計的, 那時咱們還沒意識到UCS-2的不足, 並且UTF-8尚未被髮明出來.
這也是爲何Windows對UTF8的支持如此之差的緣由之一吧.

後記

說了這麼多, 如今讓咱們回到一開始的問題, 若是有人問你"Unicode,GBK和UTF-8有什麼區別?", 我想你應該知道該怎麼回答了吧: Unicode是 一種字符集, 而GBK和UTF-8都是編碼, 所以Unicode和後二者不是一類事物, 是沒法進行對比的.

相關文章
相關標籤/搜索