1. 程式人生 > >一、基礎部分-2.字符串編碼

一、基礎部分-2.字符串編碼

字符編碼

一、字符編碼歷史

1. ASCII

美國人搞了個ASCII碼表,把123abcABC%$#(數字、字母、特殊符號) ,全部用10進制的數字表示。例如數字65,代表著“A” ,ASCII碼表一共255個數字,基本代表米國常用英文和符號(其實127以後都不是太常用的了)

2. GB2312-->GBK-->GB18030

中國漢字那麽多,255個數字顯然不夠表示。所以中國人發明了GB2312(6千個常用簡體漢字),後來又搞了GBK(2萬多漢字,並且包含中日韓中的漢字),再後來又搞了一個GB18030(2.7萬漢字,包含中國的少數民族語言)

3. Unicode

每個國家不同語言,都需要有自己的編碼表,很麻煩。

而且如果日本的軟件出口到中國,中國電腦一打開就會亂碼(因為沒裝日本的編碼表,軟件內文字會亂碼)。
再或者,如果一個文件中包含日語、中文、英文,那打開後就亂碼了。於是,Unicode國際統一碼(2-4個字節)誕生了。 至少用16個2進制表示:11111111 11111111 (2個bytes)

Unicode其實是一張很大的對應關系表,對應著1個字符,在unicode的位置,以及這個字符在ascii碼中的位置。
所以,無論你使用何種編碼,都能轉換成unicode碼,而unicode碼又能轉換成任何其他編碼(可以理解unicode左手牽著你編碼的字符,右手牽著其他國家的各種編碼,額unicode是千手觀音,有很多手)。

這樣的話,無論你使用GBK、ASCII、或者其他國家對字符進行編碼,都能通過這張大表,找到對應其他編碼的位置。

4. UTF-8

使用unicode全球人民都很高興,終於不再出現亂碼了,但是美國、英國人不高興了。
因為以前他們都使用ASCII碼,存一個電影1個GB,現在用Unicode變成了2個GB了(因為ASCII碼一共255個數字就可以表示美國人使用的常用字符,255轉換成二進制:1111 1111(十進制轉二進制),是1個bytes。而Unicode至少2個bytes,所有使用unicode編碼的文件,就會比ascii碼多一倍大小)

於是乎,為了優化unicode,節省字節。又搞出了UTF-8(英文繼續用1個字節,歐洲用2個字節,東南亞用3個字節),註意:使用unicode的時候中文是2個字節,現在使用UTF8變成了3個字節表示了,這占空間啊,很蛋疼,但沒辦法。

5. 其他知識:

  • 數據文件存到硬盤上是2進制的,像這樣: 01010100100101001(盡然沒逗號,那計算機怎麽斷句呢?其實計算機會一次性讀取8位2進制,不足8位的補0)

  • 你使用ascii碼對你的文件編碼,並保存到硬盤。當你打開的時候(就是讀到內存的時候),就必須也要指定使用ascii碼打開。如果你錯誤的使用GBK打開,那肯定亂碼了。總的來說,什麽方式存進去的,什麽方式讀出來。

  • windows系統默認使用GBK編碼,也就是說,當你存一個文件到磁盤的時候,如果你不指定使用什麽編碼,默認使用GBK給你編碼(Mac、 Linux系統默認UTF8編碼)

二、Python2與Python3編碼不同

  • Python3 讀取文件的流程:

    1. Python3解析器,按照文件頭定義的編碼進行解析。(如果你在文件頭,不指定編碼方式。python3默認使用UTF-8去讀取)
    2. Python3自動把你的編碼格式,轉換成Unicode到內存中。
    3. 按照代碼內容語義分析,去解析執行。
    4. 所有的變量、字符都是以Unicode編碼去聲明

總結:在Python3中,無論讀取中文、英文或其他國家語言編碼的文件,在打印的時候都不會亂碼,因為都進行了自動轉換,變成了Unicode。(現在的所有操作系統,都支持Unicode編碼)

  • Python2 讀取文件的流程:

  1. Python2默認使用ASCII編碼,去嘗試讀取文件。
  2. Python2把ASCII編碼加載到內存中(註意:不會像Python3一樣,幫你自動轉換成Unicode到內存哦)
  3. 如果你文件中包含中文,直接就亂碼了,因為ASCII表中沒有中文啊。
  4. 別急,通常在編寫Python2文件時候,都在文件頭加:#coding: utf-8,目的是想告訴Python2解釋器,在讀取文件時,別在使用默認的ASCII了,要使用UTF-8編碼去讀取。
  5. 這下,終於把代碼以UTF-8讀取到內存了。
  6. 還沒完,如果你使用Windows電腦,Windows默認打印終端使用GBK編碼,在終端打印的時候,你發現依然還是亂碼了(崩潰了~)。
  7. Windows是支持Unicode或GBK打印的,所以,還需要你把內存中的UTF-8手動轉換成Unicode或者GBK,才能正常打印。
  8. 如何手動轉換呢:
    • UTF8 ---> Unicode 把UTF8轉換成Unicode的過程,稱為:解碼(decode)
    • Unicode ---> UTF8/GBK 把unicode編碼轉換成UTF8或者GBK的過程,稱為:編碼(encode)
  • 總結一下:

  1. Python3文件默認編碼UTF-8(默認用utf-8讀到內存),在內存中,python3的字符串編碼是unicode(Python3自動幫你把utf-8轉成了unicode到內存中了)
  2. Python2文件默認編碼ASCII碼(默認用ASCII碼讀到內存),在內存中,python2字符串默認你按照什麽編碼讀出來的,在內存就是什麽編碼(如果你聲明了文件頭,就按照頭聲明來編碼讀到內存中)。
  3. Python2中,Unicode是個單獨的數據類型。

三、深入Python2字符編碼

  • 1. 問題出現了

    想判斷數據類型,使用type()函數。python2中unicode是一種單獨的數據類型,而GBK和UTF-8使用type()函數判斷類型時,都返回str類型。這樣的話,我怎麽知道,到底是GBK還是UTF8呢?有人說可以使用len()函數去判斷,因為GBK是使用2個字符代表一個中文字,而UTF8使用3個字符代表一個中文字。如下:

#!/usr/bin/env python2.7
#encoding:utf-8
s = "中"

#unicode
s2 = s.decode("utf-8")
print "我運行在python2中,我聲明了文件頭是UTF8。雖然python2不會自動把我轉換成unicode,但我可以自己使用decode函數手工轉換,你看,我現在就變成了unicode編碼了,我在python2中表現的數據類型是:%s " %type(s2)

#GBK
s3 = s2.encode("gbk") #註意:s2經過上一步的轉換,已經變成了unicode編碼了。s3現在相當於,在用encode函數把unicode編碼成了GBK。
print "我是gbk編碼的,我在python2中的數據類型是:%s, 我的長度是:%s" % (type(s3),len(s3))

#UTF8
s4 = s2.encode("utf-8")
print "我是utf-8編碼的,我在python2中的數據類型是:%s , 我的長度是:%s " %(type(s4),len(s4))
#運行結果:
我運行在python2中,我聲明了文件頭是UTF8。雖然python2不會自動把我轉換成unicode,但我可以自己使用decode函數手工轉換,你看,我現在就變成了unicode編碼了,我在python2中表現的數據類型是:<type ‘unicode‘>

我是gbk編碼的,我在python2中的數據類型是:<type ‘str‘>, 我的長度是:2

我是utf-8編碼的,我在python2中的數據類型是:<type ‘str‘> , 我的長度是:3

問題確實存在,gbk編碼、utf-8編碼,在python2中的數據類型都是str。
我如何知道一個字符串到底是gbk編碼的還是utf8編碼的呢?
雖然可以看長度,gbk用2個字節,代表1個中文,utf-8用3個字節,代表1個中文漢字。這沒錯,那是因為舉得例子簡單,如果無數個字節,比如這樣:‘\xd6\xd0...bulabulabula一大堆‘,讓你判斷,這些字節的編碼是什麽?那怎麽辦呢?

  • 2. 難道是巧合?

1. 突然想起來,有那個Unicode對應關系表啊..(http://www.unicode.org/charts/ 下載:CJK Unified Ideographs (Han) )
>>> s = "中"
>>> s_unicode = s.decode("utf-8")
>>> s_unicode
u‘\u4e2d‘    
>>>
>>> s_gbk = s_unicode.encode("gbk")
>>> s_gbk
‘\xd6\xd0‘  
>>>
>>> s_utf8 = s_unicode.encode("utf-8")
>>> s_utf8
‘\xe4\xb8\xad‘ 

總結下,這個"中"字:
unicode編碼是長這樣: u‘\u4e2d‘    
gbk編碼是長這樣的: ‘\xd6\xd0‘
UTF8編碼是長這樣的: ‘\xe4\xb8\xad‘
2. 趕緊下載,並打開Unicode對應表:
  • 搜索"中"字,恩,"中"字旁邊的Unicode確實寫著:4E2D。
  • 但我們關心的是GBK啊,GBK上寫著: G0-5650(16進制)。
  • 這5650 和 我們上面打印的‘\xd6\xd0‘也不完全對啊,但是貌似對了2位。6和0對了,5沒對。
3. gbk編碼打印是這樣的:‘\xd6\xd0‘,這是16進制,換成2進制試試看。
  • d6(16進制)--->11010110(2進制) PS: 4位2進制,代表1個16進制,所以 1101 代表d(13) 0110代表6
  • d0(16進制)--->11010000(2進制)
4. 即便把16進制換成2進制,感覺也和G0-5650沒啥關系啊,這樣,把上面的2進制的第一位,去掉不算****,試試看:
  • \xd6\xd0 中的d6,11010110(2進制)--->1101 0110--->去掉每組第1位不算--->0101 0110--->轉成16進制--->56

  • \xd6\xd0 中的d0, 11010000(2進制)--->1101 0000--->去掉每組第1位不算--->0101 0000)--->轉成16進制--->50
5. 我擦嘞~,當去掉1位後,就正好是5650。和Unicode表上的GBK編碼對應上了...為什麽要去掉1位呢?
  • 3. 原來是這樣

GBK是兼容ASCII碼的,如何實現的?

首先:GBK是每2個字節代表1個中文,ASCII碼是1個字節代表1個英文字母的。

如果給你一個2個2進制(2個字節)11010110 11010000,你怎知道是代表1個中文,還是2個ASCII碼的英文字符呢?

正好,ASCII碼常用的就127個,7位2進制最大就是127,就可以表示了,還剩下1位。

中國人設計GBK的時候,考慮到想兼容ASCII碼。想到,既然ASCII碼的127往後至255都不常用。那我們利用剩下的這1位做文章。

  • [x] 如果,2個2進制(2個字節),每個2進制的第1位,都被設置成了1,它就是中文。
  • [x] 如果,1個2進制(1個字節),第一位是1,那就是按照ASCII編碼,是英文。

舉例:11010110 11010000 這2個二進制,第1位都被都設置了1,這是個中文。

總結:

  • GBK編碼中,為了兼容ASCII碼,1個2進制的字節,第一位,如果設置成1,叫高字節。如果設置成0,代表低字節。2個連續的高字節,就是中文。**
  • Unicode表中,"中"字符--> 本來應該用d6d0表示,但是它忽略了高字節--->所以變成了這樣 G0-5650
  • 其實,UTF-8 也是利用了高字節,來區分ASCII編碼的。不然一堆連續的2進制,怎麽判斷啊?

四、Python str和bytes類型

1. python2中的str和bytes沒啥區別

python2
>>> s = "中"
>>> print s
中               #稱之為字符串 (其實是字形)
>>> s
‘\xe4\xb8\xad‘   #字符串"中"字,在編碼表裏的位置。 是二進制數據串(只不過16進制表現形式)

其實按理說,print 應該打印 ‘\xe4\xb8\xad‘ 這個位置,但是print幫你打印了這個位置對應的"中"字 (其實這個"中"字就是一個圖片字形)

總結:

  1. 根據2進制的數據串,在編碼表中找到對應的關系,然後在找到的那個字形,就是字符串。
  2. 一堆二進制數據串兒,python稱他為bytes(註意有 s結尾,復數)。
  3. 概念上"中"字符串(字形)和 ‘\xe4\xb8\xad‘二進制串兒bytes 不是一種東西,但是本質上,卻都指向了相同的一個東西。
  4. 所以,python2中,直接把字符串"str" 類型等於了 bytes類型。

實例:

#python2裏,把bytes類型和字符串類型,都稱為字符串"str",不區分,如下:。

>>> s_str = "中"
>>> print type(s_str)
<type ‘str‘>   #字符串就是str類型
>>>
>>> s_bytes = b"中"   #b"字符串" -->定義一個bytes類型
>>> print type(s_bytes)
<type ‘str‘>   #bytes類型 也是 str類型
>>>

2. 既然python2的bytes類型和字符串str是一會兒事兒,為什麽還要搞一個bytes類型呢( b"字符串" ) ?

  • 因為之所以能顯示中文字形,是因為二進制串兒bytes,在Unicode編碼表中找到了對應的圖形,然後給展示出來。
  • 但圖片、視頻讀到內存中,圖片沒編碼(沒unicode這種東西),那怎麽辦呢?實際上,圖片是一堆2進制流,對應成了屏幕像素快顏色,最終展示出來的。
  • 既然圖片是一堆二進制流,那麽這一堆數據流,怎麽表示他們呢?就只能用bytes類型表示。
  • print圖片會亂碼,因為圖片又不是文字,如果你非要print圖片,那print就會嘗試把圖片裏的二進制流到unicod編碼表中找對應關系,但最終肯定會亂碼的。
  • 也就是說,字符、圖片、視頻等,都是bytes類型。但字符因為有Unicode那張表存在,可以找到對應關系。
    所以字符中的bytes,可以轉成字符串str(字形),而字符串"str",也肯定能轉換成bytes。
  • 但圖片、音頻中的bytes,則不能轉換成字符串str(因為他們本來也不是字,是圖啊..)

3. Python3 數據保存和傳輸

  • Python3的字符串類型是str,在內存中以Unicode表示,一個字符對應若幹個字節。如果要在網絡上傳輸,或者保存到磁盤上,就需要把str變為以字節為單位的bytes,像這樣:x = b‘ABC‘

  • 以Unicode表示的str通過encode()方法可以編碼為指定的bytes,例如:
#Python3
>>> type("ABC")
<class ‘str‘> 

>>> ‘ABC‘.encode(‘ascii‘)  #把英文的str變成了bytes
b‘ABC‘

>>> ‘中文‘.encode(‘utf-8‘) #把中文的str變成了bytes
b‘\xe4\xb8\xad\xe6\x96\x87‘
  • 反過來,如果我們從網絡或磁盤上讀取了字節流,那麽讀到的數據就是bytes。要把bytes變為str,就需要用decode()方法:
>>> b‘ABC‘.decode(‘ascii‘)  #把英文bytes轉換成了str
u‘ABC‘

>>> type(b‘ABC‘.decode(‘ascii‘)) #通過type()函數查看,python3中str就是unicode類型。
<type ‘unicode‘>

>>> b‘\xe4\xb8\xad\xe6\x96\x87‘.decode(‘utf-8‘) #把中文的bytes轉成str
u‘\u4e2d\u6587‘
>>> print(b‘\xe4\xb8\xad\xe6\x96\x87‘.decode(‘utf-8‘)) #打印時,就可以顯示字形了。
中文

#說明:如果bytes中包含無法解碼的字節,decode()方法會報錯
  • len()函數計算的是str的字符數,如果換成bytes,len()函數就計算字節數:
>>> len(‘ABC‘)
3
>>> len(‘中文‘)
2
>>>
>>> len(b‘ABC‘)
3
>>> len(b‘\xe4\xb8\xad\xe6\x96\x87‘)
6

4. 總結

  • Python2
  1. str = bytes
  2. 為什麽要有bytes類型?因為要表示圖片和音頻等,二進制格式的數據。
  3. python2,即便以utf-8編碼的字符串,在windows上仍然不能顯示(windows默認是GBK)。
    怎樣才能正常顯示?手動轉unicode編碼decode("utf8")。
    還有一個辦法,在pythno2中,定義一個字符串時,在前面加個u,例如:u"中",這時候打印類型print type,結果: <type ‘unicode‘>
  4. python2 中,unicode類型可以表示字符串,還可以使用str表示字符串。
    有點兒亂,但是unicode是為了顯示全球文字,而設計的類型。如果只有英文字符,str類型就夠了。
  5. 以utf-8、gbk等編碼的文件,加載到內存時,還依然是 utf-8、gbk (不幫你轉換unicode,讀到什麽,就是什麽)
    所以說,python2中,如果以gbk格式編碼的文件,加載到內存還是gbk,在print打印時,就是gbk格式的bytes。
  • Python3
  1. 以utf-8 gbk等編碼的文件,加載到內存時,自動幫你轉換成unicode。所以,在python3中看到的str字符串,都是unicode格式編碼的bytes。
  2. 也就是說python3,默認支持了全球化語言(因為str都是unicode編碼的),
  3. python3中已經沒有了單獨的unicode類型了,因為str就是Unicode,既str = unicode。
  4. 在python2中,str = bytes,之所以多個bytes類型是為了表示圖片等。而python3 str是str (str = unicode), bytes就是bytes,沒關系。
  5. 註意,在內存中可以是unicode展示,這沒問題。但當你存儲到硬盤、網絡傳輸,當然不能用unicode存(unicode只是張對照表),需要unicode轉換成gbk或utf-8編碼格式的,然後換成bytes去存儲或傳輸。
    也就是說,python3中 str(unicode編碼)就僅僅是打印使用的,真正字符串傳輸、或者存盤,都會轉換成bytes。
  • 那為啥Python2有 str、Unicode,而Python3就只有str類型了(python3中str就是Unicode)?

因為在python2初期開發的時候,就沒考慮到全球化問題,默認用的ASCII碼。後來python普及後,各國開發者呼籲支持全球化語言,所以python2沒辦法,單獨又造了一個Unicode類型。

#python2實例
>>> s = "str"
>>> s_unicode = s.decode("utf-8")   #python2中,unicode類型是單獨的。
>>> s_gbk = s_unicode.encode("gbk") #把unicode編碼成gbk
>>> type(s_gbk)
<type ‘str‘>            #打印時發現,gbk編碼的類型顯示為str。

#python3實例
>>> s = "str"         #python3 字符串str都是unicode
>>> s_gbk = s.encode("gbk") #把unicode編碼成gbk
>>> type(s_gbk)
<class ‘bytes‘>       #打印時發現, gbk編碼的類型是 bytes。(不再和python2一樣是str類型了)

結論:
1. 在python2中,str = bytes,之所以多個bytes類型是為了表示圖片等二進制流。
2. 在python3中,字符串str就是str字形 (str都是由unicode編碼), bytes就是bytes,明確告訴你,你想看字符串str的字形,必須的是unicode編碼。而圖片二進制流,沒辦法編碼和解碼,傳輸時一律使用bytes類型。

參考鏈接:

  • " rel="nofollow">python 之路,致那些年,我們依然沒搞明白的編碼
  • 廖雪峰-字符串和編碼

一、基礎部分-2.字符串編碼