不少程式語言中的 字符串 都是由 字符數組 (或稱為 字節序列 )來表示, C 語言就是這樣。
char msg[] = "Hello, world!";由於一個字節最多只能表示 256 種字符,用來表示英文字符綽綽有餘,想覆蓋非英文字符便捉襟見肘了。為了表示眾多的非英文字符(比如漢字),計算機先驅們發明了 多字節編碼 ——通過多個字節來表示一個字符。由於原始字節序列不維護編碼信息,操作不慎便導致各種亂碼現象。
Python 提供的解決方案是 Unicode 字符串 ( str )對象, Unicode 可以表示各種字符,無需關心編碼。然而存儲或者網絡通訊時,字符串對象不可避免要 序列化 成字節序列。為此, Python 額外提供了字節序列對象 —— bytes 。
如上圖, str 對象統一表示一個 字符串 ,不需要關心編碼;計算機通過 字節序列 與存儲介質和網絡介質打交道,字節序列由 bytes 對象表示;存儲或傳輸 str 對象時,需要將其 序列化 成字節序列,序列化過程也是 編碼 的過程。
好了,我們已經弄明白 str 對象以 bytes 之間的關係,這兩者是 Python 中最重要的內建對象之一。讀者對 str 對象應該再熟悉不過了,但對更接近底層的 bytes 對象可能涉獵不多。沒關係,經過本節學習,你將徹底掌握它!
對象結構bytes 對象用於表示由若干字節組成的 字節序列 以及相關的 操作 ,並不關心字節序列的 含義 。因此, bytes 應該是一種 變長對象 ,內部由 C 數組實現。Include/boolobject.h 頭文件中的定義印證了我們的猜測:
typedef struct {
PyObject_VAR_HEAD
Py_hash_t ob_shash;
char ob_sval[1];
/* Invariants:
* ob_sval contains space for 'ob_size+1' elements.
* ob_sval[ob_size] == 0.
* ob_shash is the hash of the string or -1 if not computed yet.
*/
} PyBytesObject;字節序列對象 PyBytesObject 中,確實藏著一個字符數組 ob_sval 。注意到 ob_sval 數組長度定義為 1 ,這是 C 語言中定義 變長數組 的技巧。這個技巧在前面章節( int 對象,永不溢出的整數 )中介紹過,這裡不再贅述。源碼注釋表明, Python 為待存儲的字節序列額外分配一個字節,用於在末尾處保存 \0 ,以便兼容 C 字符串。
此外,我們還留意到另一個欄位 ob_shash ,它用於保存字節序列的 哈希值 。Python 對象哈希值應用範圍很廣,比如 dict 字典對象依賴對象哈希值進行存儲。由於計算 bytes 對象哈希值需要遍歷其內部的字符數組,開銷相對較大。因此, Python 選擇將哈希值保存起來,以空間換時間,避免重複計算。
最後,以幾個典型例子結束 bytes 對象結構介紹,以此加深理解:
由此可見,就算空 bytes 對象( b'' )也是要佔用內存空間的,至少變長對象 公共頭部 是少不了的。
>>> sys.getsizeof(b'')
33bytes 對象佔用的內存空間可分為以下個部分進行計算:
變長對象公共頭部 24 字節,ob_refcnt 、 ob_type 、 ob_size 每個欄位各佔用 8 字節;因此, bytes 對象空間計算公式為
學習了 bytes 對象的結構,接下來我們還會探索 bytes 對象的行為以及 python 是如何利用 字節緩衝池 來優化單字節 bytes 對象(也可稱為 字符對象 )的創建效率。點擊 「閱讀原文」,獲取更多細節!
😘 原創不易,求贊,求在看