對於單純做前端或者後端的同學來說,一般很難接觸到編碼問題,因為在同乙個平台上,一般都是使用同一種編碼方式,自然問題不大。但對於寫爬蟲的同學來說,編碼很可能是遇到的第乙個坑。這是因為字串無法直接通過網路被傳輸(也不能直接被儲存),需要先轉換成二進位制格式,再被還原。因此凡是涉及到通過網路傳輸字元的地方,通常都容易遇到編碼問題。
為了方便解釋,我們首先來定義一些概念。每個開發者都知道字串,它是一些字元的集合, 比如hello world
就是乙個最常見的字串。相對來說,字元比較難定義一些。從語義上來講,它是組成字串的最基本單位,比如這裡的字母、空格,以及標點、特定語言(中文、日文)、emoji 符號等等。
字元是語言中的概念,但是計算機只認識 0 和 1 這兩個數字。因此要想讓計算機儲存、處理字串,就必須把字串用二進位制表示出來。在 ascii 碼中,每個英文本母都有自己對應的數字。我們通常把 ascii 碼稱為字符集,也就是字元的集合。了解 ascii 碼的同學應該都知道小寫字母 a 可以用 97 來表示,97 也被稱為字元a
在 ascii 字符集中的碼位。
如果要設計一種密碼,最簡單的方式就是把字母轉換成它在 ascii 碼中的碼位再傳送,接受者則查詢 ascii 碼表,還原字元。可見把字元轉換成碼位的過程類似於加密(encrypt),我們稱之為編碼(encode),反則則類似於解密,我們稱之為解碼(decode)
字元轉換成碼位的過程是編碼,這個過程有無數種實現方式。比如a -> 97
、b -> 98
這種就是 ascii 編碼,因為 255 = 2 ^ 8,所以所有 ascii 編碼下的碼位恰好都可以由乙個位元組表示。
ascii 比較誕生得比較早,隨著越來越多的國家開始使用計算機,0-255 這麼點碼位肯定不夠用了。比如中國人為了展示漢字,發明了 gb2312 編碼。gb2312編碼完全向下相容 ascii 編碼,也就是說所有 ascii 字符集中的字元,它在 gb2312 編碼下的碼位與 ascii 編碼下的碼位完全一致,而中文則由兩個位元組表示,這也就是為什麼早期我們一般認為乙個中文等於兩個英文的原因。
除了中國人自己的編碼方式,各個地區的人也都根據自己的語言拓展了相應的編碼方式。那麼問題就來了, 給你乙個碼位0xee 0xdd
,它到底表示什麼字元,取決於它是用哪種編碼方式編碼的。這就好比你拿到了密文,但沒有密碼表一樣。因此,要想正確顯示一種語言,就必須攜帶這個語言的編碼規範,要想正確顯示世界上所有的語言,看起來就比較困難了。
因此 unicode 實際上是一種統一的字符集規範,每乙個 unicode 字元由 6 個十六進製制數字表示,因此理論上可以表示出16 ^ 6 = 16777216
個字元,顯然是綽綽有餘了。
看起來 unicode 就是一種很棒的編碼方式。誠然,unicode 可以表示所有的字元,但過於全面帶來的缺點就是過於龐大。對於字元a
來說,如果使用 ascii 編碼,可以表示為 0x61,只要乙個位元組就能存下,但它的 unicode 碼位是 0x000061,需要三個位元組。因此採用 unicode 編碼的英文內容,會比 ascii 編碼大三倍。這大大增加了檔案本地儲存時占用的空間以及傳輸時的體積。
因此,我們有了對 unicode 字元再次編碼的編碼方式,常見的有 utf-8,utf-16 等。utf 表示 unicode transfer format,因此是針對 unicode 字符集的一系列編碼方式。utf-8 是一種變長編碼,也就是說不同的 unicode 字元在 utf-8 編碼下的碼位長度可能不同,如下表所示:
unicode 編碼(16進製制)
utf-8 碼位(二進位制)
000000-00007f
0******x
000080-0007ff
110***xx 10******
000800-00ffff
1110***x 10****** 10******
010000-1fffff
11110***10******10******10******
這個表有兩點值得注意。乙個是 ascii 字符集中的所有字元,它們的 utf-8 碼位依然占用乙個位元組,因此 utf-8 編碼下的英文本元不會向 unicode 一樣增加大小。另乙個則是所有中文的 utf-8 碼位都占用 3 個位元組,大於 gbk 編碼的 2 位元組。因此如果存在明確的業務需要,是可以用 gbk 編碼取代 utf-8 編碼的。
儘管 utf-8 非常常用,但它可變長度的特點不僅會導致某些場景下內容過大,也為索引和隨機讀取帶來了困難。因此在很多作業系統的記憶體運算中,通常使用 utf-16 編碼來代替。utf-16 的特點是所有碼位的最小單位都是 2 位元組,雖然存在冗餘,但易於索引。由於碼位都是兩個位元組,就會存在位元組序的問題。因此 utf-16 編碼的字串,一開頭會有幾個位元組的 bom(byte order markd)來標記位元組序,比如0xff 2
(fe0x55,254) 表示 intel cpu 的小字節序,如果不加 bom 則預設表示大字節序。需要注意的是,某些應用會給 utf-8 編碼的位元組也加上 bom。
雖然看起來問題變得複雜了,為了儲存/傳輸乙個字元,竟然需要兩次編碼,但別忘了,unicode 編碼是通用的,因此可以內置於作業系統內部。所以我們平時所謂的對字串進行 utf-8 編碼,其實說的是對字串的 unicode 碼位進行 utf-8 編碼。
這一點在 python3 中得到了充分的體現,字串由字元組成,每乙個字元都是乙個 unicode 碼位。
如果把編譯碼理解成利用密碼表進行加解密,那麼就容易理解,為什麼編碼和解碼過程都是易錯的。
如果被編碼的 unicode 字元,在某種編碼中根本沒有列出編碼方式,這個字元就無法被編碼:
city = 'são paulo'
b_city = city.encode('cp437')
# unicodeencodeerror: 'charmap' codec can't encode character '\xe3' in position 1: character maps to
b_city = city.encode('cp437', errors='ignore')
# b'so paulo'
b_city = city.encode('cp437', errors='replace')
# b's?o paulo'
複製**
同理,如果被解碼的碼位,在編碼表中找不到與之對應的字元,這個碼位就無法被解碼:
octets = b'montr\xe9al'
s_octest1 = octets.decode('utf8')
# unicodedecodeerror: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte
s_octest1 = octets.decode('cp1252')
# montréal
s_octest1 = octets.decode('iso8859_7')
# montrιal
s_octest1 = octets.decode('utf8', errors='replace')
# montr�al
複製**
python 的解決方案是,encode
和decode
函式都有乙個引數errors
可以控制如何處理無法被編、解碼的內容。它的值可以是ignore
(忽略這個錯誤並繼續執行),也可以是replace
(用系統的佔位符填充)。
一般來說,無法從碼位推斷出編碼方式,就像你不可能從密文推斷出加密方式一樣。但是某些編碼方式會留下非常顯著的特徵,一旦這些特徵頻繁出現,基本就可以斷定編碼方式。python 提供了乙個名為chardet
的包,可以幫助開發者推斷出編碼方式,並且會給出相應的置信度。置信度越高,說明是這種編碼方式的可能性越大。
octets = b'montr\xe9al'
chardet.detect(octets)
# octets.decode('iso-8859-1')
# montréal
複製**
編碼是為了把人類人類可讀的字元轉換成計算機容易儲存和傳輸的二進位制,解碼反之,編碼後得到的結果稱之為碼位。
unicode 是一種通用字符集,從字元到 unicode 字符集中碼位的轉換也可以叫做 unicode 編碼
unicode 編碼對英文本元不友好,因此出現了針對 unicode 碼位的再次編碼,比如 utf-8,希望在節省空間的同時保留強大的表達能力
各個編碼之間的關係如下圖所示:
字串編碼
1.unicode 的編碼方式 編碼類似1小時和60分鐘的關係,本質的時間刻度還是相同的。unicode 編碼有 utf 8 utf 16 和 utf 32 它們都是將數字轉換到程式資料的編碼方案。utf 8 以位元組為單位。表示乙個字元時,能用乙個位元組就不用兩個或者三個位元組表示。utf 16 ...
字串與編碼
首先應該把位元組陣列看成是string的載體。dot net使用的字串string是unicode編碼的 它也是以unicode編碼的形式顯示字串。以下是用自己語言對幾個常用函式的說明 自己總結的,反正看不明msdn bytes system.text.encoding.unicode.getbyt...
Python字串編碼
在python中有些特殊的地方是存在兩種字串,分別為str和unicode字串,他們都繼承自basestring。如 s hello world s為str us u hello world us為unicode。使用help str 和help unicode 可以檢視各自說明,他們都有decod...