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)存中(你打開(kāi)電腦上的一個(gè)文件是要從硬盤(pán)讀取到內(nèi)存中的),統(tǒng)一使用Unicode編碼娶聘。在需要保存到硬盤(pán)或需要傳輸時(shí)灵临,就轉(zhuǎn)化為UTF-8編碼(由上篇文章可知,這樣可以節(jié)省空間趴荸,提高傳輸速度)儒溉。
如,在記事本編輯時(shí)发钝,從文件讀取的UTF-8字符被轉(zhuǎn)化為Unicode字符到內(nèi)存里顿涣,編輯完成,保存時(shí)在將內(nèi)存中的Unicode字符轉(zhuǎn)化為UTF-8保存到文件:
瀏覽網(wǎng)頁(yè)時(shí)酝豪,服務(wù)器會(huì)把動(dòng)態(tài)生成的Unicode字符轉(zhuǎn)化為UTF-8字符再傳輸?shù)綖g覽器:
所以你看到很多網(wǎng)頁(yè)的源碼上會(huì)有類似<meta charset="UTF-8" />
的信息涛碑,表示該網(wǎng)頁(yè)正在使用UTF-8編碼。
在python中孵淘,字符串是以Unicode編碼的蒲障,而python的字符串類型是str
,內(nèi)存中以Unicode表示瘫证。要在網(wǎng)絡(luò)上進(jìn)行傳輸或保存到磁盤(pá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
,其超過(guò)了ascii編碼的范圍么抗,會(huì)報(bào)錯(cuò)毅否。
bytes
中,無(wú)法顯示為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ò)上或磁盤(pán)中讀取到了字節(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)我們用記事本打開(kāi)bmp
, exe
, jpg
文件時(shí)吨枉,會(huì)出現(xiàn)一大堆亂碼:
這是因?yàn)樗鼈儾皇俏谋疚募嵌M(jìn)制文件哄芜,而二進(jìn)制文件包含很多無(wú)法顯示和打印的字符貌亭,所以,如果想讓記事本這樣的文本處理軟件能處理二進(jìn)制數(shù)據(jù)认臊,就需要一個(gè)二進(jìn)制到字符的轉(zhuǎn)換方法圃庭,Base64是一種最常見(jiàn)的二進(jìn)制編碼方法。
注意:通常我們說(shuō)編碼都是將字符編碼成二進(jìn)制失晴,將二進(jìn)制解碼為字符剧腻。而現(xiàn)在我們說(shuō)的是將二進(jìn)制編碼為字符文本,將字符文本解碼為二進(jìn)制涂屁。不要弄混恕酸,筆者在最開(kāi)始學(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)頁(yè)中正常顯示垃杖。
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)說(shuō)了可以用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í)我們就需要通過(guò)對(duì)二進(jìn)制進(jìn)行編碼讀取這些信息了。
struct bytes
與其他數(shù)據(jù)類型的轉(zhuǎn)換
Bytes之間可以進(jìn)行加法(無(wú)減法操作)組成一個(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模塊來(lái)解決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é)無(wú)符號(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é)無(wú)符號(hào)整數(shù),usigned short
跨嘉。
struct
模塊定義的數(shù)據(jù)類型可以參考python的官方文檔
相反川慌,unpack
指令就用來(lái)將字節(jié)流bytes
按給定參數(shù)轉(zhuǎn)化為我們想要的格式:
>>> struct.unpack('>I', b'\x00\x9c@c')
(10240099,)
該句指令的意思是:將給定的字節(jié)流轉(zhuǎn)換成unsigned int
類型的4字節(jié)無(wú)符號(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
模塊來(lái)完成,比如可以用struct
處理c語(yǔ)言中的結(jié)構(gòu)體寺庄。
比如有一個(gè)結(jié)構(gòu)體:
struct Header
{
unsigned short id;
char[4] tag;
unsigned int version;
unsigned int count;
}
通過(guò)socket.recv接收到了上面的結(jié)構(gòu)體數(shù)據(jù)艾蓝,存在字符串s中力崇,bytes
格式斗塘,現(xiàn)在把它解析出來(lái)赢织,可以使用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
八毯。
通過(guò)一個(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é)流,可以通過(guò)socket將這個(gè)字節(jié)流發(fā)送出去