Python中的pack和unpack的使用
不同类型的语言支持不同的数据类型,比如Go有int32、int64、uint32、uint64等不同的数据类型,这些类型占用的字节大小不同,而同样的数据类型在其他语言中比如Python中,又是完全不同的处理方式,比如Python的int既可以是有符号的,也可以是无符号的,这样一来Python和Go在处理同样大小的数字时存储方式就有了差异。
除了语言之间的差别,不同的计算机硬件存储数据的方式也有很大的差异,有的32bit是一个word,有的64bit是一个word,而且他们存储数据的方式或多或少都有些差异。
当这些不同的语言以及不同的机器之间进行数据交换,比如通过network进行数据交换,他们需要对彼此发送和接受的字节流数据进行pack和unpack操作,以便数据可以正确的解析和存储。
计算机如何存储整型
可以把计算机的内存看做是一个很大的字节数组,一个字节包含8bit信息可以表示0-255的无符号整型,以及-128—127的有符号整型。当存储一个大于8bit的值到内存时,这个值常常会被切分成多个8bit的segment存储在一个连续的内存空间,一个segment一个字节。有些处理器会把高位存储在内存这个字节数组的头部,把低位存储在尾部,这种处理方式叫big-endian,有些处理器则相反,低位存储在头部,高位存储在尾部,称之为little-endian。
假设一个寄存器想要存储0x12345678到内存中,big-endian和little-endian分别存储到内存1000的地址表示如下
address
big-endian
little-endian
1000
0x12
0x78
1001
0x34
0x56
1002
0x56
0x34
1003
0x78
0x12
计算机如何存储character
和存储number的方式类似,character通过一定的编码格式进行编码比如unicode,然后以字节的方式存储。
Python中的struct模块
Python提供了三个与pack和unpack相关的函数
struct.pack(fmt,v1,v2,...) struct.unpack(fmt,string) struct.calcsize(fmt)
第一个函数pack负责将不同的变量打包在一起,成为一个字节字符串。
第二个函数unpack将字节字符串解包成为变量。
第三个函数calsize计算按照格式fmt打包的结果有多少个字节。
pack操作
Pack操作必须接受一个templatestring以及需要进行pack一组数据,这就意味着pack处理操作定长的数据
importstruct a=struct.pack("2I3sI",12,34,"abc",56) b=struct.unpack("2I3sI",a) printb
上面的代码将两个整数12和34,一个字符串“abc”和一个整数56一起打包成为一个字节字符流,然后再解包。其中打包格式中明确指出了打包的长度:"2I"表明起始是两个unsignedint,"3s"表明长度为4的字符串,最后一个"I"表示最后紧跟一个unsignedint,所以上面的打印b输出结果是:(12,34,‘abc',56),完整的Pythonpack操作支持的数据类型见下表。
Format
CType
Pythontype
Standardsize
Notes
x
padbyte
novalue
c
char
stringoflength1
1
b
signedchar
integer
1
(3)
B
unsignedchar
integer
1
(3)
?
_Bool
bool
1
(1)
h
short
integer
2
(3)
H
unsignedshort
integer
2
(3)
i
int
integer
4
(3)
I
unsignedint
integer
4
(3)
l
long
integer
4
(3)
L
unsignedlong
integer
4
(3)
q
longlong
integer
8
(2),(3)
Q
unsignedlonglong
integer
8
(2),(3)
f
float
float
4
(4)
d
double
float
8
(4)
s
char[]
string
p
char[]
string
P
void*
integer
(5),(3)
计算字节大小
可以利用calcsize来计算模式“2I3sI”占用的字节数
printstruct.calcsize("2I3sI")#16
可以看到上面的三个整型加一个3字符的字符串一共占用了16个字节。为什么会是16个字节呢?不应该是15个字节吗?1个int4字节,3个字符3字节。但是在struct的打包过程中,根据特定类型的要求,必须进行字节对齐(关于字节对齐详见https://en.wikipedia.org/wiki/Data_structure_alignment)。由于默认unsignedint型占用四个字节,因此要在字符串的位置进行4字节对齐,因此即使是3个字符的字符串也要占用4个字节。
再看一下不需要字节对齐的模式
printstruct.calcsize("2Is")#9
由于单字符出现在两个整型之后,不需要进行字节对齐,所以输出结果是9。
unpack操作
对于unpack而言,只要fmt对应的字节数和字节字符串string的字节数一致,就可以成功的进行解析,否则unpack函数将抛出异常。例如我们也可以使用如下的fmt解析出a:
c=struct.unpack("2I2sI",a) printstruct.calcsize("2I2sI") printc#16(12,34,'ab',56)
不定长数据pack
如果打包的数据长度未知该如何打包,这样的打包在网络传输中非常常见。处理这种不定长的内容的主要思路是把长度和内容一起打包,解包时首先解析内容的长度,然后再读取正文。
打包变长字符串
对于变长字符在处理的时候可以把字符的长度当成数据的内容一起打包。
s=bytes(s) data=struct.pack("I%ds"%(len(s),),len(s),s)
上面代码把字符s的长度打包成内容,可以在进行内容读取的时候直接读取。
解包变长字符串
int_size=struct.calcsize("I") (i,),data=struct.unpack("I",data[:int_size]),data[int_size:]
解包变长字符时首先解包内容的长度,在根据内容的长度解包数据
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。