bytes是什么
由上篇文章幾種字符編碼, 我們已經(jīng)知道了ASCII Unicode UTF-8的關(guān)系。而且,計(jì)算機(jī)只能識(shí)別0和1,那顯然,文件存儲(chǔ)在計(jì)算機(jī)中也只能是以二進(jìn)制的形式存儲(chǔ),字符編碼在計(jì)算機(jī)中的工作機(jī)制是怎樣的呢?
在計(jì)算機(jī)內(nèi)存中(你打開電腦上的一個(gè)文件是要從硬盤讀取到內(nèi)存中的),統(tǒng)一使用Unicode編碼。在需要保存到硬盤或需要傳輸時(shí),就轉(zhuǎn)化為UTF-8編碼(由上篇文章可知,這樣可以節(jié)省空間,提高傳輸速度)。
如,在記事本編輯時(shí),從文件讀取的UTF-8字符被轉(zhuǎn)化為Unicode字符到內(nèi)存里,編輯完成,保存時(shí)在將內(nèi)存中的Unicode字符轉(zhuǎn)化為UTF-8保存到文件:
瀏覽網(wǎng)頁時(shí),服務(wù)器會(huì)把動(dòng)態(tài)生成的Unicode字符轉(zhuǎn)化為UTF-8字符再傳輸?shù)綖g覽器:
所以你看到很多網(wǎng)頁的源碼上會(huì)有類似<meta charset="UTF-8" />
的信息,表示該網(wǎng)頁正在使用UTF-8編碼。
在python中,字符串是以Unicode編碼的,而python的字符串類型是str
,內(nèi)存中以Unicode表示。要在網(wǎng)絡(luò)上進(jìn)行傳輸或保存到磁盤中,就需要將str
轉(zhuǎn)化為以字節(jié)為單位的bytes
。
要獲取字符的bytes
表示,可以使用encode()
方法,如
>>> 'ABC'.encode('ascii')
b'ABC'
>>>'ABC'.encode('utf-8')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
純英文的str
,可以用ascii編碼為bytes
,其內(nèi)容與utf-8相同。含中文的str
不能用ascii編碼為bytes
,其超過了ascii編碼的范圍,會(huì)報(bào)錯(cuò)。
bytes
中,無法顯示為ASCII字符的字節(jié),會(huì)以b\x##
的形式顯示。
使用type可以查看b'abc'或b'\xe4\xb8\xad\xe6\x96\x87'的數(shù)據(jù)類型,是一個(gè)bytes類
>>> type(b'\xe4\xb8\xad\xe6\x96\x87')
<class 'bytes'>
相反,從網(wǎng)絡(luò)上或磁盤中讀取到了字節(jié)流,讀到的就是bytes
,需要用decode()方法解碼為str
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
字節(jié)字符串與文本字符串
b'ABC' 與 'ABC'是不同的,前者是bytes
,也叫字節(jié)字符串;后者是str
,也稱為文本字符串。前者一個(gè)字符占一個(gè)字節(jié)(中文一個(gè)漢字占三個(gè)字節(jié)),str
類型在內(nèi)存中以Unicode表示,一個(gè)字符占若干字節(jié)。
>>> len('下'.encode('utf-8'))
3
在讀取二進(jìn)制數(shù)據(jù)的時(shí)候,字節(jié)字符串和文本字符串可能會(huì)引起錯(cuò)誤。特別需要注意的是,索引和迭代動(dòng)作返回的是字節(jié)的值而不是字節(jié)字符串。
>>> # Text string
>>> t = 'Hello world'
>>> for x in t:
... print(x)
...
H
e
l
l
o
w
o
r
l
d
>>> # Byte string
>>> b = b'Hello world'
>>> for x in b:
... print(x)
...
72
101
108
108
111
32
119
111
114
108
100
Base64:顯示與打印二進(jìn)制數(shù)據(jù)
Base64是一種用64個(gè)字符表示任意二進(jìn)制數(shù)據(jù)的方法。
當(dāng)我們用記事本打開bmp
, exe
, jpg
文件時(shí),會(huì)出現(xiàn)一大堆亂碼:
這是因?yàn)樗鼈儾皇俏谋疚募?,是二進(jìn)制文件,而二進(jìn)制文件包含很多無法顯示和打印的字符,所以,如果想讓記事本這樣的文本處理軟件能處理二進(jìn)制數(shù)據(jù),就需要一個(gè)二進(jìn)制到字符的轉(zhuǎn)換方法,Base64是一種最常見的二進(jìn)制編碼方法。
注意:通常我們說編碼都是將字符編碼成二進(jìn)制,將二進(jìn)制解碼為字符。而現(xiàn)在我們說的是將二進(jìn)制編碼為字符文本,將字符文本解碼為二進(jìn)制。不要弄混,筆者在最開始學(xué)的時(shí)候全程懵逼,完全搞不明白到底是在解碼還是編碼。
方法:
準(zhǔn)備一個(gè)包含64個(gè)字符的數(shù)組:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行處理,每三個(gè)字節(jié)一組,共3 * 8 = 24
bit,劃為4組,每組6個(gè)bit:
我們得到四個(gè)數(shù)字作為索引,查表,得到對(duì)應(yīng)的4個(gè)字符,就是編碼后的字符串。
所以,我們是將3個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)編碼為4個(gè)字節(jié)的文本數(shù)據(jù),長(zhǎng)度增加33%,好處是編碼后的文本可以在郵件正文、網(wǎng)頁中正常顯示。
python
內(nèi)置的base64
模塊可以提供base64的編解碼功能:
>>> import base64
>>> base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
b'abcd++//'
>>> base64.b64decode(b'abcd++//')
b'i\xb7\x1d\xfb\xef\xff'
當(dāng)要編碼的二進(jìn)制數(shù)據(jù)不是3的倍數(shù),最后會(huì)剩下1或2個(gè)字符時(shí)怎么辦?Base64會(huì)自動(dòng)在末尾用b\x00
補(bǔ)足后在進(jìn)行解碼,再在編碼的結(jié)尾加上1或2個(gè)=
,以表示在二進(jìn)制數(shù)據(jù)末尾加了幾個(gè)b\x00
。
但是,在很多Base64編碼中會(huì)把=
去掉,因?yàn)樗鼤?huì)在URL,Cookies中造成歧義:
# 標(biāo)準(zhǔn)Base64:
'abcd' -> 'YWJjZA=='
# 自動(dòng)去掉=:
'abcd' -> 'YWJjZA'
去掉=
怎么解碼呢,因?yàn)锽ase64編碼后的長(zhǎng)度永遠(yuǎn)是4的整數(shù)倍,所以將不是4的整數(shù)倍的Base編碼自動(dòng)添加相應(yīng)數(shù)量=
后使其變?yōu)?的整數(shù)倍后再解碼即可
你可能會(huì)想,上面我們已經(jīng)說了可以用decode解碼二進(jìn)制數(shù)據(jù),為什么現(xiàn)在還需要對(duì)二進(jìn)制數(shù)據(jù)編碼再顯示呢?
原因是:上文針對(duì)的是文本文件中的字符,而像jpg
bmp
mp3
等二進(jìn)制格式文件,其中的二進(jìn)制數(shù)據(jù)不能正常解析為字符:
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x56\x87'.decode('utf-8')
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 3-4: invalid continuation byte
可很多時(shí)候我們會(huì)在一些文件頭加一些與文件屬性有關(guān)的數(shù)據(jù),如在jpg文件頭加數(shù)據(jù)表示該圖片的大小、分辨率、色彩等信息,這時(shí)我們就需要通過對(duì)二進(jìn)制進(jìn)行編碼讀取這些信息了。
struct bytes
與其他數(shù)據(jù)類型的轉(zhuǎn)換
Bytes之間可以進(jìn)行加法(無減法操作)組成一個(gè)新的bytes
:
>>> m = b'hello '
>>> b = b'world'
>>> m+b
b'hello world'
>>> m+b'world'
b'hello world'
根據(jù)前文已知,將字符轉(zhuǎn)換成二進(jìn)制可以使用encode()
方法,那如果是非字符型數(shù)據(jù)如整數(shù)、浮點(diǎn)數(shù)怎么轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)呢?
Python提供了一個(gè)struct模塊來解決bytes
與其他數(shù)據(jù)類型之間的轉(zhuǎn)換:
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'
pack
第一個(gè)參數(shù)是處理指令,'>I'
的意思是:
>
表示字節(jié)順序是big-endian,也就是網(wǎng)絡(luò)序,I
表示4字節(jié)無符號(hào)整數(shù),unsigned int
。
后面參數(shù)個(gè)數(shù)要與處理指令一致,大小也要在指定的參數(shù)范圍內(nèi):
>>> struct.pack('>2H', 10245599)
Traceback (most recent call last):
File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)
>>> struct.pack('>2H', 102456565599)
Traceback (most recent call last):
File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)
H
表示整數(shù),兩字節(jié)無符號(hào)整數(shù),usigned short
。
struct
模塊定義的數(shù)據(jù)類型可以參考python的官方文檔
相反,unpack
指令就用來將字節(jié)流bytes
按給定參數(shù)轉(zhuǎn)化為我們想要的格式:
>>> struct.unpack('>I', b'\x00\x9c@c')
(10240099,)
該句指令的意思是:將給定的字節(jié)流轉(zhuǎn)換成unsigned int
類型的4字節(jié)無符號(hào)整數(shù)。unpack
同樣也可以將字節(jié)流轉(zhuǎn)換為字符數(shù)據(jù),更換參數(shù)即可。
unpack
返回的是tuple類型
應(yīng)用場(chǎng)景:
有時(shí)需要用python處理二進(jìn)制數(shù)據(jù),比如存取文件,socket操作時(shí)。這時(shí)可以用python的struct
模塊來完成,比如可以用struct
處理c語言中的結(jié)構(gòu)體。
比如有一個(gè)結(jié)構(gòu)體:
struct Header
{
unsigned short id;
char[4] tag;
unsigned int version;
unsigned int count;
}
通過socket.recv接收到了上面的結(jié)構(gòu)體數(shù)據(jù),存在字符串s中,bytes
格式,現(xiàn)在把它解析出來,可以使用unpack
函數(shù):
import struct
id, tag, version, count = struct.unpack('!H4s2I', s)
!
表示網(wǎng)絡(luò)字節(jié)順序,因?yàn)閿?shù)據(jù)是從網(wǎng)絡(luò)上接收到的,再網(wǎng)絡(luò)上傳送時(shí)他是網(wǎng)絡(luò)字節(jié)順序的。后面的H4s2I
表示1個(gè)unsigned int
,4s
表示4字節(jié)的字符串,2個(gè)unsigned short
。
通過一個(gè)unpack
就將id, tag, version, count數(shù)據(jù)解析好了。
同樣,也可以使用pack
再將本地?cái)?shù)據(jù)pack成struct
格式
ss = struct.pack('>I4s2I', id, tag, version, count)
pack
函數(shù)按照指定格式轉(zhuǎn)換成了結(jié)構(gòu)體Header,ss現(xiàn)在是一個(gè)字節(jié)流,可以通過socket將這個(gè)字節(jié)流發(fā)送出去