您好,登錄后才能下訂單哦!
不同類型的語言支持不同的數據類型,比如 Go 有 int32、int64、uint32、uint64 等不同的數據類型,這些類型占用的字節大小不同,而同樣的數據類型在其他語言中比如 Python 中,又是完全不同的處理方式,比如 Python 的 int 既可以是有符號的,也可以是無符號的,這樣一來 Python 和 Go 在處理同樣大小的數字時存儲方式就有了差異。
除了語言之間的差別,不同的計算機硬件存儲數據的方式也有很大的差異,有的 32 bit 是一個 word,有的 64 bit 是一個 word,而且他們存儲數據的方式或多或少都有些差異。
當這些不同的語言以及不同的機器之間進行數據交換,比如通過 network 進行數據交換,他們需要對彼此發送和接受的字節流數據進行 pack 和 unpack 操作,以便數據可以正確的解析和存儲。
計算機如何存儲整型
可以把計算機的內存看做是一個很大的字節數組,一個字節包含 8 bit 信息可以表示 0-255 的無符號整型,以及 -128—127 的有符號整型。當存儲一個大于 8 bit 的值到內存時,這個值常常會被切分成多個 8 bit 的 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 操作必須接受一個 template string 以及需要進行 pack 一組數據,這就意味著 pack 處理操作 定長 的數據
import struct a = struct.pack("2I3sI", 12, 34, "abc", 56) b = struct.unpack("2I3sI", a) print b
上面的代碼將兩個整數 12 和 34,一個字符串 “abc” 和一個整數 56 一起打包成為一個字節字符流,然后再解包。其中打包格式中明確指出了打包的長度: "2I" 表明起始是兩個 unsigned int , "3s" 表明長度為 4 的字符串,最后一個 "I" 表示最后緊跟一個 unsigned int ,所以上面的打印 b 輸出結果是:(12, 34, ‘abc', 56),完整的 Python pack 操作支持的數據類型見下表。
Format | C Type | Python type | Standard size | Notes |
---|---|---|---|---|
x | pad byte | no value | ||
c | char | string of length 1 | 1 | |
b | signed char | integer | 1 | (3) |
B | unsigned char | integer | 1 | (3) |
? | _Bool | bool | 1 | (1) |
h | short | integer | 2 | (3) |
H | unsigned short | integer | 2 | (3) |
i | int | integer | 4 | (3) |
I | unsigned int | integer | 4 | (3) |
l | long | integer | 4 | (3) |
L | unsigned long | integer | 4 | (3) |
q | long long | integer | 8 | (2), (3) |
Q | unsigned long long | 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” 占用的字節數
print struct.calcsize("2I3sI") # 16
可以看到上面的三個整型加一個 3 字符的字符串一共占用了 16 個字節。為什么會是 16 個字節呢?不應該是 15 個字節嗎?1 個 int 4 字節,3 個字符 3 字節。但是在 struct 的打包過程中,根據特定類型的要求,必須進行字節對齊(關于字節對齊詳見 https://en.wikipedia.org/wiki/Data_structure_alignment) 。由于默認 unsigned int 型占用四個字節,因此要在字符串的位置進行4字節對齊,因此即使是 3 個字符的字符串也要占用 4 個字節。
再看一下不需要字節對齊的模式
print struct.calcsize("2Is") # 9
由于單字符出現在兩個整型之后,不需要進行字節對齊,所以輸出結果是 9。
unpack 操作
對于 unpack 而言,只要 fmt 對應的字節數和字節字符串 string 的字節數一致,就可以成功的進行解析,否則 unpack 函數將拋出異常。例如我們也可以使用如下的 fmt 解析出 a :
c = struct.unpack("2I2sI", a) print struct.calcsize("2I2sI") print c # 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:]
解包變長字符時首先解包內容的長度,在根據內容的長度解包數據
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。