python中編碼問題


寫在前面:

本文是很基礎的東西,這些基礎的東西有一個特點,看一遍會了,但其中很多精髓其實被忽略了,建議你貨比三家,細細品嘗編碼之美。還有,這文章是我熬夜寫的,可能有錯,請批判性閱讀,謝謝。

0x00:為社么會出現多種編碼?

相信計算機專業的都知道,所有的數據(文本,音頻,視頻等等)在計算機內部都是以二進制形式來表示的。而計算機內部為什么采用二進制則是由硬件決定的(計算機采用了具有兩種穩定狀態的二值電路)。這樣,就引出一個問題:
我們人類不適合直接看二進制。

因此,需要用一種方法,將二進制轉為我們能看懂的東西。編碼就應運而生了。


0x01:編碼發展歷史

第一階段:

在計算機中,所有的數據只可能是0或者1(用高電平和低電平分別表示1和0),那么我們通常看到的字符也就只能用0和1來表示呀。於是科學家們(這里指的是美國的科學家)就想出一個辦法,把一個特定的數字對應一個特定的字母進行存儲和傳輸,比如我需要存儲字母a,那么我存入一個數字97(即在計算機中存入二進制(01100001),這個過程叫做編碼(encode),而我們在讀取數據的時候,當遇到97時,我們就讓計算機顯示字母a,這個過程叫做解碼(decode)。

這里你應該知道:

計算機看懂的東西我們看不懂,我們看懂的東西,計算機看不懂。

把計算機看懂的東西(二進制(01100001))變成我們看懂的東西(數字97,也就是a),這個過程叫解碼(decode)
把我們看懂的東西(數字97,也就是a)變成計算機看懂的東西(二進制(01100001)),這個過程叫做編碼(encode)

為了大家在數據傳輸的時候不至於產生誤會,那么我們需要讓所有的人都使用數字97來代表字母a,所以需要制定一份標准(即碼表),最開始的這個標准叫做ASCII碼表。

**ASCII碼的實現方式: **
最早的計算機在設計時采用8個比特(bit)作為一個字節(byte),所以,一個字節能表示的最大的整數就是255(二進制11111111=十進制255)。
由於計算機是美國人發明的,因此,最早只有127個字符被編碼到計算機里(即用一個字節的后七位),也就是大小寫英文字母、數字和一些符號,這個編碼表被稱為ASCII編碼,比如大寫字母A的編碼是65,小寫字母z的編碼是122。


第二階段:

隨着發展,計算機開始普及,當計算機流傳到歐洲時,問題再次出現,原本的ASCII編碼只能解決美國人的編碼問題,無法將歐洲的文字表示出來。於是乎,歐洲人就把ASCII碼中沒用到的第一位給用了,即:

  1. ASCII碼用一個字節的后七位,表示范圍是0-127;
  2. 歐洲人把這個字節的第一位也用了,表示范圍0-255。除去原本的0-127,剩下128-255.128-159之間為控制字符,160-255位文字符號,其中包括了西歐語言、希臘語、泰語、阿拉伯語、希伯來語。磚家們決定把他們的編碼名稱叫做Latin1,后面由於歐洲統一制定ISO標准,所以又有了一個ISO的名稱,即ISO-8859-1。

第三階段

計算機技術當然也傳到了亞洲大地,比如中國。原本的一個字節的8個位全都用完了,但是要處理中文顯然一個字節是不夠的,至少需要兩個字節,而且還不能和ASCII編碼沖突,所以,中國制定了GB2312編碼,用來把中文編進去。

問題又來了:

你可以想得到的是,全世界有上百種語言,日本把日文編到Shift_JIS里,韓國把韓文編到Euc-kr里,各國有各國的標准,就會不可避免地出現沖突,結果就是,在多語言混合的文本中,顯示出來會有亂碼。

因此,Unicode應運而生。Unicode把所有語言都統一到一套編碼里,這樣就不會再有亂碼問題了。

到了這里:已經知道的編碼方式主要有兩種:ASCIIUnicode

現在,捋(lǚ)一捋ASCII編碼和Unicode編碼的區別:ASCII編碼是1個字節,而Unicode編碼通常是2個字節。(如果要用到非常偏僻的字符,就需要4個字節)

  • 字母A用ASCII編碼是十進制的65,二進制的01000001;
  • 字符'0'用ASCII編碼是十進制的48,二進制的00110000,注意字符'0'和整數0是不同的;
  • 漢字中已經超出了ASCII編碼的范圍,用Unicode編碼是十進制的20013,二進制的01001110 00101101。

**你可以猜測,如果把ASCII編碼的A用Unicode編碼,只需要在前面補0就可以,因此,A的Unicode編碼是00000000 01000001。 **

新的問題又出現了

如果統一成Unicode編碼,亂碼問題從此消失了。但是,如果你寫的文本基本上全部是英文的話,用Unicode編碼比ASCII編碼需要多一倍的存儲空間,在存儲和傳輸上就十分不划算。

所以,本着節約的精神,又出現了把Unicode編碼轉化為“可變長編碼”的UTF-8編碼。

  • UTF-8編碼把一個Unicode字符根據不同的數字大小編碼成1-6個字節,常用的英文字母被編碼成1個字節,漢字通常是3個字節,只有很生僻的字符才會被編碼成4-6個字節。如果你要傳輸的文本包含大量英文字符,用UTF-8編碼就能節省空間。
字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 10111000 10101101

從上面的表格還可以發現,UTF-8編碼有一個額外的好處,就是ASCII編碼實際上可以被看成是UTF-8編碼的一部分,所以,大量只支持ASCII編碼的歷史遺留軟件可以在UTF-8編碼下繼續工作。


0x10:ASCII,Unicode,utf-8之間的關系

看完上面的東西,可能有點亂,我這里簡單總結一下ASCII,Unicode,utf-8之間的關系:

  1. ASCII是計算機剛剛起步的時候用得編碼方式,特點是可以表示的字符特別少,但簡單易用。
  2. Unicode是隨着計算機發展,ASCII已經無法表示世界各國這么多文字而出現的一個新的編碼方式,優點是編碼快速,缺點是占用內存大。(如果你的文本全都是英語,如果用Unicode時,每個字符占用兩個字節。而用ASCII每個字符只占用一個字節);
  3. 為了解決Unicode的內存占用大問題,出現了utf-8,utf-8可以根據字符的類型,自動選擇最優編碼。但缺點是編碼速度慢。

所以有了令人容易搞混的問題出現:

什么時候用utf-8(這里utf-8已經包括ASCII,ASCII編碼實際上可以被看成是UTF-8編碼的一部分)編碼方式?什么時候用Unicode編碼方式?


什么時候用utf-8編碼?

答案很顯然:對內存消耗要求高的,對速度要求不高的場景下用utf-8。(注意:這里的速度是指cpu運算速度)
那你就想啊,什么時候對內存消耗要求高?很容易地就想到當我們要保存在硬盤的時候肯定是想占用空間越少越好啊,當我們在網絡上傳輸肯定也想占用空間越少越好啊,所以,當我們的數據保存在硬盤的時候,當我們數據要在網絡傳輸的時候,用得就是utf-8編碼。


社么時候用Unicode?

答案顯然:對速度要求特別高的,相對之下占用空間大小可以稍微妥協的場景下用Unicode編碼。
我們知道,數據想被處理,首先得加載都內存上,這樣,cpu才能以非常驚人的速度再內存上獲取要處理的數據。這樣,我們就輕易知道,當數據被加載到內存上時,在內存中的編碼方式是Unicode。

我們來個總結:

  1. 我們平時電腦磁盤中的一個文件(abc.txt)其實是以utf-8編碼方式存儲的,當我們打開這個文件時,這個文件在加載到內存的時候會轉變為Unicode編碼方式。

  2. 瀏覽網頁的時候,服務器會把動態生成的Unicode內容轉換為UTF-8再傳輸到瀏覽器(所以你看到很多網頁的源碼上會有類似<meta charset="UTF-8" />的信息,表示該網頁正是用的UTF-8編碼。):


Python字符串

在最新的Python 3版本中,字符串是以Unicode編碼的,也就是說,Python的字符串支持多語言。這里,我重現這句話:在python中,字符串是以Unicode編碼的。這句話的一個重要的地方是:字符串,例如:
>>> print("這句話是使用Unicode編碼的,支持多語言,比如English.")
這句話是使用Unicode編碼的,支持多語言,比如English.

解釋:print函數輸出的這句話在python里是使用Unicode編碼的(當然它此時也在內存中,因為它現在正被加載着嘛)


那我想看看以utf-8編碼方式輸出的時候是什么樣的,怎么做?很簡單,encode函數接受一個參數,這個參數可以指定以什么方式編碼!看:

>>> "這句話是使用Unicode編碼的,支持多語言,比如English.".encode('utf-8')
b'\xe8\xbf\x99\xe5\x8f\xa5\xe8\xaf\x9d\xe6\x98\xaf\xe4\xbd\xbf\xe7\x94\xa8Unicode\xe7\xbc\x96\xe7\xa0\x81\xe7\x9a\x84\xef\xbc\x8c\xe6\x94\xaf\xe6\x8c\x81\xe5\xa4\x9a\xe8\xaf\xad\xe8\xa8\x80\xef\xbc\x8c\xe6\xaf\x94\xe5\xa6\x82English.'

看這個輸出十分有趣,首先,輸出是以b開頭的,說明這是一段bytes。(bytes的作用請看下去)有沒有想起在前面說過的utf-8是向下兼容ASCII碼的?你看輸出中的英文UnicodeEnglish就被原樣輸出,而ASCII碼不能識別的中文,則用utf-8編碼方式來表示,如\xe8,\xbf等等。


那Unicode編碼方式用得好好的,可以直接混合輸出英文和中文等多種語言,換成ctf-8輸出字符只有英文能讓我們看懂,中文變成了難以分辨的十六進制(\xe8\xbf\x99\xe5\x8f\xa5\xe8...),我們為什么還要有utf-8編碼方式呢?
想到這個問題說明你已經get到點了。你想,utf-8編碼方式的優點是社么?
就是省內存啊
那么,由於Python的字符串類型是str在內存中以Unicode編碼的,一個字符對應若干個字節。如果要在網絡上傳輸,或者保存到磁盤上,就需要把Unicode編碼的str變為以字節為單位的bytes,而通過utf-8編碼或者ASCII碼編碼生成的結果就是以字節為單位的bytes
這句話這么長無非就重復一個觀點:

python中的str是以Unicode編碼的(注意,既然我們能看到str,說明這個python文件已經被打開了,即已經加載到內存上),如果要在網絡上傳輸,或者保存到磁盤上,就得轉換為utf-8編碼方式。


這里,我通過講解三個例子,你們體會體會:

>>> 'I love computer'.encode('ascii')
b'I love computer'

解釋:由於'I love computer'是純英語,所以可以用ASCII編碼。
再看:'I love computer'和'b'I love computer''有什么不同?沒錯,多了一個b。這個b大有玄妙之處:

  1. 'I love computer'是python中的str,是以Unicode方式編碼的。
  2. 'b'I love computer''也是python中的str,但它是以ASCII碼編碼的。

那能不能用utf-8編碼'I love computer'呢?答案顯然(ASCII編碼實際上可以被看成是UTF-8編碼的一部分):

>>> 'I love computer'.encode('utf-8')
b'I love computer'

好了,前面是對純英文的str進行編碼,那對中文的str編碼呢?可以對中文的str進行utf-8編碼,不能進行ASCII碼編碼(為什么呢?自己想吧):

>>> '我喜歡計算機'.encode('utf-8')
b'\xe6\x88\x91\xe5\x96\x9c\xe6\xac\xa2\xe8\xae\xa1\xe7\xae\x97\xe6\x9c\xba'
>>> '我喜歡計算機'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

這幾個例子很妙,你細細看幾遍吧。 (認准有b和沒b的區別)
全文完
參考:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431664106267f12e9bef7ee14cf6a8776a479bdec9b9000


免責聲明!

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



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