week1:擴展知識:字符編碼(下)

一:字符編碼簡介

         1:ASCII

         最初的計算機的使用是在美國,所用到的字符也就是現在鍵盤上的一些符號和少數兒個特殊的符號,一個字節所就能足以容納所有的這些字符,實際上表示這些字符的字節最高位都爲0,也就是說這些字節都在0到127之間,如字符a對應數字97。這套編碼規則被稱爲ASCII(美國標準信息交換碼)

          2:GBK、GB2312

         隨着計算機的應用和普及,許多國家都把本地的字符集引入了計算機,大大擴展了計算機中字符的範圍。以中文爲例,一個字節是不能容納所有的中文漢字的,因此大陸將每一箇中文字符都用兩個字節的數字來表示,原有的ASCII字符的編碼保持不變,仍用一個字節表示,爲了將一箇中文字符與兩個ASCII碼字符相區別,中文字符的每個字節的最高位都爲1,這套編碼規則稱爲GBK(國標碼),後來,又在GBK的基礎上對更多的中文字符(包括繁體)進行了編碼,新的編碼系統就是GB2312,可見GBK是GB2312的子集。

          3:Unicode

         每個國家和地區都制定了一套自己的編碼,那麼同樣的一個字節,在不同的國家和地區就代表了不同的字符。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。

         爲了解決各個國家和地區使用本地化字符編碼帶來的不利影響,將全世界所有的符號進行了統一編碼,稱之爲Unicode編碼。這是一種所有符號的編碼。如 “中”這個符號,在全世界的任何角落始終對應的都是一個十六進制的數字4e2d。

         最初的Unicode標準UCS-2使用兩個字節表示一個字符,所以你常常可以聽到Unicode使用兩個字節表示一個字符的說法。但過了不久有人覺得256*256太少了,還是不夠用,於是出現了UCS-4標準,它使用4個字節表示一個字符,不過我們用的最多的仍然是UCS-2。

          4:UTF

         注意,UCS(Unicode Character Set)還僅僅是字符對應碼位的一張表而已,比如"中"這個字的碼位是4E2D。字符具體如何傳輸和儲存則是由UTF(UCS Transformation Format)來負責。

         一開始這事很簡單,直接使用UCS的碼位來保存,這就是UTF-16,比如,"漢"直接使用\x4E\x2D保存(UTF-16-BE),或是倒過來使用\x2D\x4E保存(UTF-16-LE)。

         這裏就有兩個嚴重的問題,第一個問題是,如何才能區別Unicode和ASCII?計算機怎麼知道兩個字節表示一個符號,而不是分別表示兩個符號呢?第二個問題是,英文字母只用一個字節表示就夠了,如果Unicode統一規定,每個符號用兩個字節表示,這對於存儲來說是極大的浪費。於是UTF-8橫空出世。

          5:UTF-8

         UTF-8是使用最廣的一種Unicode的實現方式。其他實現方式還包括UTF-16(字符用兩個字節或四個字節表示)和UTF-32(字符用四個字節表示),不過在互聯網上基本不用。重複一遍,這裏的關係是,UTF-8Unicode的實現方式之一

         UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。UTF-8的編碼規則很簡單,只有二條:

         1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。

         2)對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。

下表總結了編碼規則,字母x表示可用編碼的位。

 

Unicode符號範圍

 UTF-8編碼方式

00 00 00 00   -   00 00 00 7F

 0xxxxxxx

00 00 00 80   -   00 00 07 FF

 110xxxxx 10xxxxxx

00 00 08 00   -   00 00 FF FF

 1110xxxx 10xxxxxx 10xxxxxx

00 01 00 00   -   00 10 FF FF

 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟據上表,解讀UTF-8編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符佔用多少個字節。

         下面以漢字"嚴"爲例,演示如何實現UTF-8編碼。

         已知"嚴"的unicode是4E25(1001110 00100101),根據上表,可以發現4E25處在第三行的範圍內(00000800-0000 FFFF),因此"嚴"的UTF-8編碼需要三個字節,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然後,從"嚴"的最後一個二進制位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,"嚴"的UTF-8編碼是"11100100 10111000 10100101",轉換成十六進制就是E4B8A5。

          6:BOM

         BOM(Byte Order Mark)。UTF引入了BOM來表示自身編碼,如果一開始讀入的幾個字節是其中之一,則代表接下來要讀取的文字使用的編碼是相應的編碼:

BOM_UTF8             '\xef\xbb\xbf' 

BOM_UTF16_LE    '\xff\xfe' 

BOM_UTF16_BE    '\xfe\xff'

如果一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;如果頭兩個字節是FF FE,就表示該文件採用小頭方式。以漢字"嚴"爲例,Unicode碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在後,就是Big endian方式;25在前,4E在後,就是Little endian方式。

         一般情況下,不建議在Linux中使用BOM。

         7:Unicode與UTF-8之間的轉換

         "嚴"的Unicode碼是4E25,UTF-8編碼是E4B8A5,以該漢字爲例,利用Ultraedit查看各種編碼的具體值,一般的編輯器都支持以不同的編碼方式保存文本,編碼方式如下:

         1)ANSI是默認的編碼方式。對於英文文件是ASCII編碼,對於簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會採用Big5碼)。這種方式編碼的文件,就是兩個字節"D1 CF",這正是"嚴"的GB2312編碼:

         2)UTF16編碼指的是UCS-2編碼方式,即直接用兩個字節存入字符的Unicode碼。編碼是四個字節"FF FE 25 4E",其中"FF FE"表明是小頭方式存儲:

         3)UTF16-NOBOM編碼與上一個選項相對應,只不過沒有BOM編碼,也就是文件中只存儲UTF16編碼"25 4E":

         4)UTF-8編碼,文件中共6個字節:"EF BB BF E4 B8 A5",其中前三個爲BOM編碼,後三個爲UTF8編碼:

         6)UTF8-NOBOM編碼與上一個選項相對應,只不過沒有BOM編碼,也就是文件中只存儲UTF8編碼" E4 B8 A5":

二:Python代碼文件的編碼

         Python解釋器會使用某種編碼方式來解釋Python源代碼文件,默認情況下,這種編碼方式就是ASCII。

         Python2.1版本,在Python源碼文件中,只能以以基於Latin-1的“轉義unicode”的方式來書寫Unicode字符,這對於亞洲的程序員是很不友好的。解決該問題的方法是,在源碼文件的頂部,使用某種特殊的註釋方式來表明源碼文件的編碼。

         爲了表明源碼文件的編碼,這種特殊的註釋必須位於源碼文件的第一行或第二行,類似於:

#coding=<encodingname>    
#!/usr/bin/python  
# -*- coding:<encoding name> -*-  
#!/usr/bin/python  
# vim: setfileencoding=<encoding name> :  

這種方式的本質是:文件的第一行或第二行必須能匹配正則表達式:

” coding[:=]\s*([-\w.]+)”,該表達式中的group1就會被解釋爲編碼名稱,如果Python無法識別該編碼,則在編譯時就會報錯。

         像Windows這樣的平臺,會在Unicode文件的最開始加上BOM字節碼,UTF-8文件的字節碼是:”xef\xbb\xbf”。爲了兼容這種方式,包含這種字節碼的文件,即使沒有字節編碼註釋,也會被解釋爲”utf-8”。如果一個源碼文件,既有編碼註釋,又有UTF-8BOM字節碼,則在編碼註釋中的編碼名稱只能是”utf-8””utf8”都不行),否則會報錯:

“SyntaxError: encodingproblem: utf-8”

下面是一些使用編碼註釋的例子:

         1. Emacs風格的文件編碼註釋:

#!/usr/bin/python  
# -*- coding: latin-1 -*-  
import os, sys  
...  

 

#!/usr/bin/python  
# -*- coding: iso-8859-15 -*-  
import os, sys  
...  
!/usr/bin/python  
# -*- coding: ascii -*-  
import os, sys  
...  

2. 使用純文本方式的註釋:

# This Python file uses the following encoding:utf-8  
import os, sys  
...  

3. 沒有編碼註釋,則Python解釋器默認使用ASCII。

         如果沒聲明編碼,但是文件中又包含非ASCII編碼的字符的話,python解析器去解析的python文件,就會報錯。

#!/usr/local/bin/python  
import os, sys  
...  

4. 下面這些語法不起作用

         沒有加”coding:”前綴:

#!/usr/local/bin/python  
# latin-1  
import os, sys  
...  

編碼方式不在第一行或第二行:

#!/usr/local/bin/python  
#  
# -*- coding: latin-1 -*-  
import os, sys  
...  

不支持的編碼方式:

#!/usr/local/bin/python  
# -*- coding: utf-42 -*-  
import os, sys  
...  

5:注意:

         允許的編碼方式包括ASCII兼容編碼以及某些多字節編碼,比如SHIFT_JIS。但不包括爲所有字符都是有雙字節或者更多字節的編碼,比如UTF-16(注:也就是通常說的Unicode,但SHIFT_JIS也好,GBK也好,因爲兼容ASCII編碼,所以都可以在Python源文件裏使用)。

         如果聲明的編碼與實際不符(就是說,文件實際上是以另外的編碼保存的),出錯的可能性很大。

          文件的編碼格式決定了在該源文件中聲明的字符串的編碼,str = '哈哈'

print repr(str)

a.如果文件格式爲utf-8,則str的值爲:'\xe5\x93\x88\xe5\x93\x88'(哈哈的utf-8編碼)

b.如果文件格式爲gbk,則str的值爲:'\xb9\xfe\xb9\xfe'(哈哈的gbk編碼)

 

三:str和unicode

         str和unicode都是basestring的子類。編碼是指unicode-->str,解碼是指str-->unicode。

         str是一個字節數組,這個字節數組表示的是對unicode對象編碼(可以是utf-8、gbk、cp936、GB2312)後的存儲的格式。這裏它僅僅是一個字節流,沒有其它的含義,如果你想使這個字節流顯示的內容有意義,就必須用正確的編碼格式,解碼顯示。 對UTF-8編碼的str'哈哈'使用len()函數時,結果是6,因爲實際上,UTF-8編碼的'哈哈' == '\x e5\x93\x88\xe5\x93\x88'。

         unicode纔是真正意義上的字符串,對字節串str使用正確的字符編碼進行解碼後獲得,例如'哈哈'的unicode對象爲 u'\u54c8\u54c8' ,len(u”哈哈”) == 2

         字符串在Python內部的表示是unicode編碼,因此,在做編碼轉換時,通常需要以unicode作爲中間編碼,即先將其他編碼的字符串解碼(decode)成unicode,再從unicode編碼(encode)成另一種編碼。 

         decode的作用是將其他編碼的字符串轉換成unicode編碼,如str1.decode('gb2312'),表示將gb2312編碼的字符串str1轉換成unicode編碼。 

         encode的作用是將unicode編碼轉換成其他編碼的字符串,如str2.encode('gb2312'),表示將unicode編碼的字符串str2轉換成gb2312編碼。 

         因此,轉碼的時候一定要先搞明白,字符串str是什麼編碼,然後decode成unicode,然後再encode成其他編碼

 str.decode([encoding[, errors]])

         使用encoding指示的編碼,對str進行解碼,返回一個unicode對象。默認情況下encoding是“字符串默認編碼”,比如ascii。

errors指示如何處理解碼錯誤,默認情況下是”strict”,也就是遇到解碼錯誤時,直接拋出UnicodeError異常。其他的errors值可以有”ignore”,”replace”等。

 unicode(object[, encoding[, errors]])

         str.decode作用相同,但是該方法要快一些:http://stackoverflow.com/questions/440320/unicode-vs-str-decode-for-a-utf8-encoded-byte-string-python-2-x

str.encode([encoding[, errors]])

         返回一個經encoding編碼後的str對象,默認的encoding是”默認字符串編碼”。

errors指示如何處理解碼錯誤,默認情況下是”strict”,也就是遇到解碼錯誤時,直接拋出UnicodeError異常。其他的errors值可以有”ignore”,”replace”,'xmlcharrefreplace', 'backslashreplace'等。

         一般情況下,對Unicode對象進行編碼encode(),返回str對象。對str對象調用解碼decode(),返回unicode對象。

         但是對str調用encode也不會報錯,str.encode()實際上就等價於str.decode(sys.defaultencoding).encode().而sys.defaultencoding一般是ascii。

         同樣的,對unicode對象進行解碼unicode.decode實際上就等價於unicode.encode(sys.defaultencoding).decode()

 

四:示例

         英文字符的ASCII、UTF-8、GBK等編碼都是一樣的,因此下面的示例僅討論漢字的情況。並且保證源碼文件的實際編碼方式,和編碼註釋是一樣的。

         1:文件爲默認編碼(ASCII),無編碼註釋

 

astr ="哈哈"  

 

 運行文件,報錯:SyntaxError: Non-ASCII character '\xb9' in file 2.py on line 2, butno encoding declared; seehttp://www.python.org/peps/pep-0263.htmlfor details

 

原因:默認的文件編碼爲ASCII,這種編碼無法處理漢字,將文件保存爲GBK或者UTF-8格式,並相應的註釋聲明。

2:文件編碼爲UTF-8

# -*- coding:UTF-8 -*-  
def toHexString(s):  
    return ":".join("{0:x}".format(ord(c))for c in s)  
   
ustr =u"哈哈"  
print repr(ustr)  
print ustr, "len is ",len(ustr)  
print  
   
astr ="哈哈"  
ustr =astr.decode("UTF-8")  
print repr(ustr)  
print ustr, "len is ",len(ustr)  

運行文件,結果爲:

u'\u54c8\u54c8'

哈哈 len is  2

 

u'\u54c8\u54c8'

哈哈 len is  2

可見,上下兩種方式,其實是等價的,在源碼文件中直接使用u”...”編寫Unicode字符串,使用文件的註釋編碼,將該str解碼爲Unicode

         Python supports writing Unicode literals in anyencoding, but you have to declare the encoding being used. This is done byincluding a special comment as either the first or second line of the sourcefile。

(https://docs.python.org/2/howto/unicode.html#the-unicode-type)

3:文件爲UTF-8編碼,

 

運行文件,結果如下:

'\xe5\x93\x88\xe5\x93\x88'

鍝堝搱 len is  6

         (第一行輸出:因爲文件爲UTF-8編碼,所以輸出”哈哈”的UTF-8編碼)

         (第二行輸出:因爲print語句它的實現是將要輸出的內容傳送給終端,終端會根據默認的編碼(GBK)對輸入的字節流進行解釋,因爲 '\xe5\x93\x88\xe5\x93\x88'用GBK去解釋,其顯示的出來就是“鍝堝搱”,而且,該str的長度就是編碼的長度,爲6)

u'\u935d\u581d\u6431'

鍝堝搱 len is  3

         (第一行輸出:因爲文件是UTF-8編碼,但是卻用GBK對“哈哈”,也就是'\xe5\x93\x88\xe5\x93\x88'進行解碼,所以輸出錯誤)

         (第二行輸出:Python在向控制檯輸出unicode對象的時候會自動根據輸出環境的編碼(GBK)進行轉換,而如果輸出的不是unicode對象而是普通的str字符串,則會直接按照該字符串的編碼輸出字符串(http://noalgo.info/578.html)。所以,printustr就等價於print astr了。如果將ustr = astr.decode("gbk") 替換成 ustr =astr.decode("UTF-8"),則輸出:

u'\u54c8\u54c8'

哈哈 len is  2

54c8:54c8

         這是因爲將astr按照文件編碼註釋聲明的編碼方式進行解碼,解碼成Unicode之後,print時又自動進行GBK編碼,所以會輸出正確的字符)

 

4:打開具有漢字的文件:

         文件名可以包含Unicode字符,操作系統在處理這樣的文件時,將Unicode字符根據某種編碼進行處理,比如Max OS X使用UTF-8編碼,而Windows上的編碼是可配置的,用”mbcs”來表示當前的編碼。

         sys.getfilesystemencoding()函數可以得到當前文件系統使用的編碼,但是其實可以不用管這些,當需要打開一個包含漢字的文件時,直接使用Unicode字符即可,這樣它會自動轉換爲正確的編碼字符串:

# -*- coding:UTF-8 -*-  
   
#astr = "哈哈.txt".decode("UTF-8")  
#astr = u"哈哈.txt"  
astr ="哈哈.txt".decode("UTF-8").encode("GBK")  
try:  
    with open(astr) asfobj:  
        print fobj.read()  
except Exception, e:  
    print "error is ",e  

上面這三種方式,都可以正確打開該文件。

5、一些實例

 

參考:

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

http://www.cnblogs.com/huxi/articles/1897271.html

https://www.python.org/dev/peps/pep-0263/

http://www.crifan.com/files/doc/docbook/char_encoding/release/html/char_encoding.html

http://www.jb51.net/article/26543.htm

http://www.jb51.net/article/17560.htm

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