python語言中的編碼問題


在編程的過程當中,常常會遇到莫名其妙的亂碼問題。很多人選擇出了問題直接在網上找答案,把別人的例子照搬過來,這是快速解決問題的一個好辦法。然而,作為一個嚴謹求實的開發者,如果不從源頭上徹底理解亂碼產生的機制,並由此尋求解決問題的根本路徑,那么永遠不能從碼農的陰影中擺脫出來。下面就來一起了解一下計算機編碼問題的來龍去脈。

 

ASCII

眾所周知,計算機中的所有數據,不論是文字、圖片、視頻、還是音頻文件,本質上最終都是按照類似 01010101 的二進制形式存儲的。然而,計算機中的字符,並不能完全以這種方式來表示。由於計算機最初是由美國人發明的,因而最初的計算機編碼使用的也是美國人的標准,即ASCII( American Standard Code for Information Interchange,美國信息交換標准代碼)。ASCII碼一共規定了128個字符的編碼,比如大寫的字母A是65(二進制01000001),符號@的編碼是64(二進制01000000)。這128個符號中, 0~31及127(共33個)是控制字符或通信字符,32–126 分配給了能在鍵盤上找到並且能打印出來的字符。所有ASCII編碼表示的內容,只占用了一個字節的后面7位,最高位統一規定為0。

Image

后來為了能夠表示歐洲地區除了英文字母以外的其它字母,出現了擴展的ASCII編碼。 擴展的ASCII包含原有的128個字符,又增加了128個字符,總共是256個。編碼時最高位為1,這樣就可以與ASCII碼完全兼容。可以表示諸如音標æ(編碼145,二進制10010001)以及法語中的字母é(編碼為130,二進制10000010)等字符。

Image(1)

這個編碼能表示音標和歐洲大多數非英語系字母,但是它並不是國際標准,在不同的國家, 128 到 255對應的字符並不完全相同,這就產生了各種不同的擴展ASCII編碼。比如 ISO8859-1 字符集,也就是 Latin-1,加入了西歐常用字符,包括德法兩國的字母。ISO8859-2 字符集,也稱為 Latin-2,收集了東歐字符。 ISO8859-3 字符集,也稱為 Latin-3,收集了南歐字符,等等。

這樣的編碼方式夠嗎?顯然不夠,比如漢字,就無法用ASCII表示。擴展的ASCII 也遠遠不夠。

 

GBK

中國人為了能夠正常使用計算機這一偉大方明,做出了多方面的努力。GB2312就是這一努力的成果, 該標准於1980年發布,1981年5月1日開始實施。它標志着我國在使用電子計算機方面邁出了重要的一步。GB2312 編碼共收錄了6763個漢字,同時還兼容 ASCII。這一字符編碼基本滿足了漢字的計算機處理需要,它所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率,對一些古漢語和繁體字 GB2312 沒法處理。后來就在GB2312的基礎上創建了一種叫 GBK 的編碼,於1995年正式發布。GBK 不僅收錄了GB 2312 中的全部漢字、非漢字符號,同時還收錄了日韓語中出現的漢字,如韓國著名圍棋手李世乭中的乭 GBK編碼是0x8168(0x表示16進制)。這里可以查詢漢字對應的GBK編碼。

GBK編碼一般用兩個字節表示一個字符,如果是英文字母,則使用一個字符,與ASCII編碼相同,因此,GBK 也是兼容 ASCII 編碼的,但並不與任何擴展的ASCII編碼兼容。這可以從它的編碼序列看出來。

GBK 采用雙字節表示,總體編碼范圍為 0x8140-0xFEFE(1000000101000000-1111111011111110),首字節在 0x81-0xFE 之間,尾字節在 0x40-0xFE之間。可以看出首字節最高位都為1,這樣一來,如果尾字節后的字節最高位為0,那么就可以解析為一個ASCII編碼字符,否則就是一個連續的二字節字符。

 

Unicode

世界上存在着多種語言,有沒有一種編碼方式能夠囊括所有語言中的字符呢?答案是有。Unicode編碼正是為了滿足這種需求制定的。Unicode是一個很大的集合,目前的規模可以容納100多萬個符號。每個符號的編碼都不一樣,這么多的字符,想要以二進制形式表示,就需要比較多的字節才能夠一一對應。標准的Unicode采用4個字節表示一個字符串。這個四字節的二進制代碼,稱為這個字符的碼點。比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+ 4E6D表示漢字"乭 "。訪問unicode.org可以查詢具體的符號對應表。

使用4個字節表示一個字符的方法顯然不夠科學,因為很多英文字母只需要一個字節就可以表示了,偏要用四個字節表示就會造成很大的浪費。於是就出現了UTF-8 編碼。

Unicode只是規定了字符如何編碼,並沒有規定如何存儲和傳輸。 UTF-8編碼就是Unicode編碼的一種實現方式,它規定可以使用1~4個字節表示一個字符,根據所要表現的字符不同而變化字節長度,英文字母就用1個字節表示,漢字就用2-3個字節表示。

那么問題來了,由於計算機中的字符串是連續的0101的編碼,如何既能夠表示一個字符在Unicode編碼表中的碼點,又能夠讓計算機明白這個連續編碼串中的一個字節就是一個英文字母,而不與他前面的編碼串構成兩個或三個字節表示的字符。UTF-8 的編碼的設計者巧妙的解決了這個問題。

英文字符這些原本就可以用ASCII碼表示的字符用UTF-8表示時就只需要一個字節的空間,和ASCII是一樣的。對於多字節(n個字節)的字符,第一個字節的前n為都設為1,第n+1位設為0,后面字節的前兩位都設為10。剩下的二進制位全部用該字符的Unicode碼填充。

Unicode符號范圍 | 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

這樣的編碼方式很好理解,如果一個字節當中第一位是0,那么這個字節就對應着一個字符,如果第一位是1,那么看他后面連續有多少個1,就表示這個字符占用了多少個字節。例如,“我”的Unicode碼點是0x6211,二進制110001000010001,落在第三行的范圍內(0000 0800~0000 FFFF),因此"我"需要三個字節,格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,從"我"的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了"我"的UTF-8編碼是"11100110 10001000 10010001 ",轉換成十六進制就是E68891,這才是最終存儲在計算機中的二進制編碼。

這里指出一個誤區,網絡上有很多在線utf8編碼轉換工具,聲稱可以把漢字轉換成UTF-8 編碼,其實大多數工具只是把漢字轉換成了與之對應的unicode碼點,並不是真正在存儲和傳輸過程中的utf-8編碼。這里可以查詢漢字對應的utf-8編碼和unicode編碼,可以看出這兩者是不同的。

除了UTF-8之外,Unicode的實現方式還有UTF-16 ,UTF-32 。 UTF-16 使用2~4個字節表示一個字符,UTF-32 則使用標准的4個字節表示一個字符,與其Unicode碼點一一對應。無論采用哪種表現形式,同一字符所對應的Unicode碼點都是一樣的,只不過在存儲和傳輸的時候,把碼點做了不同的轉換。

 

PYTHON字符編碼

下面開始講講Python中的編碼問題。

Python的默認編碼是ASCII,這跟它的誕生背景有關,Python的誕生時間是1989年,Unicode於 1994年才正式公布,在Python誕生之初並無Unicode可用,只能選擇ASCII。后來做了多方改進,才使得它適用於非英語系的用戶。

如果不做修改,Python將使用ASCII為所有代碼編碼,包括注釋。

>>> import sys

>>> sys.getdefaultencoding()

'ascii'

在編寫python代碼時如果不指定文件的編碼方式,將默認使用ASCII編碼。所以如果在代碼中出現中文,將會報錯

#stringtest.py

print '你好'

C:\Python27\python.exe D:/MyGit/demo/test/test.py
File "D:/MyGit/demo/test/test.py", line 1
SyntaxError: Non-ASCII character '\xe4' in file D:/MyGit/demo/test/test.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

 

如果想在代碼中使用中文,則一定要在代碼開頭(第一行或第二行)聲明此文件的編碼方式,比如編碼方式設為UTF-8

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

或者

#!/usr/bin/python

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

其中第一行注釋表示這是一個可以在Unix/Linux/Mac直接運行的程序,Windows系統會忽略這個注釋。

 

這樣,在代碼中就可以使用中文了。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM