談談字符編碼的問題

jopen 9年前發布 | 14K 次閱讀 Unicode Python Python開發

在使用Python進行Web開發的時候,早晚會碰到字符編碼的問題,這里以一種比較通俗的方式深入講解一下字符編碼的原理。

為什么需要編碼

這個問題比較簡單,最直接的回答就是:因為計算機內部只能表示0或者1,再底層一點講,就是電路的開與關。但是實際應用中,人類對信息的編碼是采用文字和符號這種方式。因此,我們需要把0和1映射到人類日常需要的文字和符號,我們也把這種映射關系稱作編碼方式。

都有哪些編碼方式

ASCII,UTF-8,GB2312,這些都是字符編碼。對于程序員而言,比較關心的還是ASCII和UTF-8。

先說說ASCII編碼,ASCII采用一個字節進行編碼的方式,首位是0,也就是說一共可以保存2的7次方,128種不同的符號。這個編碼方式對于英文及其常用符號地區是夠用了。但是隨著發展,其它國家也要給自己的文字和符號進行編碼。比如中國,就要給漢字進行編碼。然而如果每個國家都給自己的文字和符號進行一次編碼。那么最后就會變的無法管理,可能同一個二進制子節,在不同的地方出現不同的編碼方式。隨著互聯網的發展,世界范圍內的信息交流和溝通變的更加頻繁。于是出現Unicode字符編碼,這種編碼出現的目的就是為了形成世界范圍內通用的編碼。這里說下Unicode的編碼形式:Unicode最開始采用兩個字節進行編碼,也就是說最大能容忍的編碼個數為2的16次方,65536個。這個數字基本上可以滿足全世界的字符編碼需求了。當然如果要編碼中國的所有漢字,那肯定是不夠的,但是編碼通用還是足夠了,后來Unicode編碼長度擴展到3個到4個字節。因此,你只要記住下面這一點

  • Unicode編碼方式是一種包含世界各種使用符號的編碼集合。

Unicode編碼的出現,似乎已經解決編碼這個難題,我們所有的字符編碼都采用Unicode編碼就好了。但是總是有歷史包袱的,在這里不得不多說一句,在計算機的世界里,你漸漸會發現有很多疑難雜癥,或者難以理解的知識點,有很多都是歷史原因把整個問題變的復雜了。在Unicode編碼方式出現之前,ASCII編碼已經廣泛使用一段時間了,于是,接下來要處理一個非常棘手的問題:要如何才能區別當前這個文件是采用了哪種編碼呢?估計大家最直接的想法就是在每個文件的開頭加一個標識符,表示它的編碼方式就可以了。但是很顯然我們不能把當前所有的文件回收回來然后打上標識符之后再統一發回去。于是對于Unicode編碼方式也不能僅僅是針對它的編碼集進行存儲了。隨著互聯網的興起,交流變的更加頻繁,對同于統一編碼的呼聲越來越高,因此,是時候說說UTF-8了。

UTF-8本身不算是一種編碼方式,它僅僅是Unicode編碼的一種存儲方式(當然你也可以認為它也是一種編碼方式,具體后面會談到)。Unicode對世界范圍內做了編碼規范,也可以認為是一種編碼的表,在這張表里面每個字符都有與自己對應的16個二進制位。但是對于英文字母而言,ASCII的一個字節編碼方式已經足夠了,如果都統一換成Unicode的編碼方式進行存儲,那么對存儲空間的浪費是無法忍受的。于是UTF-8出現了,它是一種變長的Unicode編碼實現方式。它和Unicode編碼是一一對應的關系,具體實現如下

對于ASCII編碼能夠搞定的字符編碼,UTF-8也使用一個字節,最高位使用0,其余和ASCII編碼一致。注意這里并不是UTF-8自己定義的編碼方式,而是Unicode的編碼規范,只是真正的Unicode編碼會把前面的一個字節全部變為0。這是一個字節的情況,對于n個字節(前面說過UTF-8是一種變長的編碼方式),其中第一個字節的前n位都是1,n+1位是0,后面的字節的前兩位統一是10,剩下的沒有提到的二進制位全部是這個符號的Unicode編碼。根據上面的描述,我們可以注意到一個UTF-8的編碼不會超過7個字節,但是這個長度也足夠它編碼世界范圍內的字符了。因此UTF-8編碼的存儲方式往往如下

0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

其中x可以填充具體字符的Unicode編碼,下面舉一個具體的例子,例如中文 你好 ,其UTF-8編碼如下

>>> s = "你好"
>>> s
'\xe4\xbd\xa0\xe5\xa5\xbd'

轉換成二進制表示如下

其UNICODE編碼如下

>>> s = u"你好"
>>> s
u'\u4f60\u597d'

轉換成二進制表示如下

對照我上文對UTF-8編碼的存儲方式是一致的。

亂碼的形成

上面討論了UTF-8的編碼實現方式,以及它和ASCII編碼是如何和諧相處的。但是這里我們要開始談談中文編碼,中文編碼不少系統默認是采用GB2312編碼,GB2312編碼具體方式可以閱讀相關資料進行詳細了解,但是有一點,那就是GB2312編碼與UTF-8編碼并不是互斥的。也就是說當我們在不知道編碼方式的情況下解碼一段文本的時候,是無法判斷該文本是什么編碼的,于是只能靠猜測算法去猜測當前文本是什么編碼,但是對于文本量比較小的時候,相同的編碼,使用UTF-8和GB2312都可以解開,這個時候就可能會導致亂碼的出現。例如“聯通”這兩個漢字的GB2312編碼是 C1AA,CDA8 ,轉換成二進制方式: 11000001 10101010,11001101 10101000 ,這段編碼恰巧符合URF-8的編碼方式,可以把它當成兩個雙字節的UTF-8編碼,這樣就造成了亂碼現象的產生。因此,總結來說,亂碼就是因為歷史原因導致沒有統一的編碼規范,程序在反解已編碼的字符的時候沒有按照同一種編碼方式工作。

Python中的字符編碼問題

在Python的源代碼中,我們經常會在文件的開頭部分看到這么一行文本

# -*- coding: utf-8 -*-

大多數Python開發人員都知道這是用來指定編碼格式的,但是在這里我要告訴你,這行代碼僅僅是用來指定當前代碼文本使用UTF-8編碼,方便Python解釋器在讀取代碼文本的時候正確識別代碼文本文件中的字符。但是同時你的編輯器在保存的時候也要使用UTF-8編碼格式才可以,否則你在源代碼文件標明是UTF-8編碼,實際卻使用了另外一種編碼,這就是欺騙了,這是第一個問題,不過這個問題對于Python開發者來說,一般很少遇到。

Python2.x中對于字符串有兩種表示方法, str 和 unicode , unicode 更像是一個復雜類型,通常表示一個Unicode對象。而 str 則是一個基礎類型,在Python中它也是基礎的不能再基礎了,它僅僅表示字符數組([]byte)。而大多數Python中的中文字符編碼問題歸根結底都是因為這個原因。

'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

因為 str 僅僅是字符數組,因此,即使你把某一段中文使用UTF-8編碼的時候,它的存在形式還是字符數組,當你使用 len 內置方法去求值的時候,你看到的肯定不是中文漢字的個數。這個時候怎么辦呢?我們通常會把它轉成unicode對象,然后進行操作,這樣得到的結果就回是我們預期的。那上面的這段編碼錯誤一般在哪些情況下會報出來呢?這里就要說到Python的 隱式轉換 ,當一個str類型變量和一個unicode類型變量進行連接操作的時候,或者對一個str對象使用 encode 方法的時候我,Python內部都會嘗試將當前的str對象轉換成 unicode 對象,然后在進行操作。那當把str對象轉換成 unicode 對象的時候,采用什么編碼呢?問題就在于此。它會采用 sys.getdefaultencoding() 方法返回的編碼方式,很不幸的是,往往返回的是都是 ascii 。而實際上你的str對象里面保存的是UTF-8編碼的字符數組,而Python默認卻會使用 ascii 去轉換,這個時候就報出上面的錯誤了。

我們該如何去避免這種編碼錯誤的問題發生呢?解決方法有幾個,分別按照優雅方式列舉一下。我們在Python進行開發的時候,對字符串統一使用 unicode 對象來表示,尤其是帶中文的字符串,千萬不要使用str類型,這樣就從根源上避免了Python隱式從 str 轉成 unicode 的可能性。對于外部傳遞進來的參數,尤其是網絡調用傳入的參數,必須先轉成 unicode 類型再進行后續的操作。這種方式比較干凈,純粹。

還有一種方式,就是在Python源碼文件的開始處加上下面三行代碼

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

這種方式會改變Python默認從 str 轉成 unicode 采用的編碼方式。只要保證我們的中文字符統一采用UTF-8編碼方式,這種方式也能很好解決字符編碼問題。但是每個文件總是寫上這三行代碼看上去會比較 dirty 。還是第一種方式比較徹底,干凈一些。

總結

Python的中文編碼問題

  1. 源代碼文件需要指定文本的編碼方式
  2. Python的str類型僅僅是字符數組,同時還有一種 unicode 類型用于表示Unicode字符串,而在對str進行某些操作的時候,Python會隱式地將str轉成unicode對象,而這個轉換的編碼方式是依靠 sys.getdefaultencoding() 的返回值

來自: http://cloudaice.com/byte-encode/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!