《Python原始碼剖析》之 str

2021-08-07 07:17:14 字數 4435 閱讀 9183

定義

typedef

struct pystringobject;

說明

pyobject_var_head

pystringobject是變長物件, 比定長物件多了乙個ob_size欄位

ob_shash

儲存字串的hash值, 如果還沒計算等於-1

當string_hash被呼叫, 計算結果會被儲存到這個字段乙份, 後續不再進行計算

用來快取當前字串的雜湊值,這在以字串作為key的dict物件查詢時非常有用。

ob_sstate

如果是interned, !=0, 否則=0

interned後面說

char ob_sval[1]

這個ob_sval比較巧妙,定義的是乙個字元的陣列,他的作用其實是作為實際分配記憶體後,字串緩衝區的指標。在實際給字串物件分配記憶體時,python申請的記憶體大小為pystringobject + 字串長度。字元指標指向一段記憶體, char陣列指標, 指向乙個ob_size+1大小陣列(c中字串最後要多乙個字元\0表字串結束)

構造方法 pystring_fromstring
//預設未初始化, 均為null

static pystringobject *characters[uchar_max + 1];

static pystringobject *nullstring;

pyobject *

pystring_fromstring(const

char *str)

// 長度=0, 且nullstring已定義, 返回nullstring

if (size == 0 && (op = nullstring) != null)

// 字元緩衝池邏輯

// 長度=1, 且characters[*str & uchar_max]字元已定義

if (size == 1 && (op = characters[*str & uchar_max]) != null)

// 申請記憶體空間

/* inline pyobject_newvar */

op = (pystringobject *)pyobject_malloc(pystringobject_size + size);

if (op == null)

return pyerr_nomemory();

// 初始化

pyobject_init_var(op, &pystring_type, size);

op->ob_shash = -1; //未計算hash, -1

op->ob_sstate = sstate_not_interned; //未intern, 0

// 將字元陣列拷貝到pystringobject

py_memcpy(op->ob_sval, str, size+1);

// 在nullstring和字元緩衝池對應位置未初始化時, 會走到這個邏輯

/* share short strings */

if (size == 0) else

if (size == 1)

// 返回

return (pyobject *) op;

}

說明

和整數物件一樣,字串物件同樣有物件池機制提供對ascii碼物件的快速引用。其原理是,當字串被建立時,pystring_fromstring函式會檢查字串長度,如果長度為1,則先建立字串,然後intern字串,最後將字串物件指標儲存在characters物件池中。以後訪問字串,如果長度為1,就直接從物件池中返回物件指標。

步驟簡化

1. 計算長度

2. 長度0, 空字串, 是返回已定義好的nullstring

3. 長度1, 字元, 返回字元緩衝池裡面的

4. 都不是, 分配記憶體, 初始化

結論

長度0/長度1, 會用到intern機制

注意, intern機制對長度》1的字串也適用

interned機制
對於相同的兩個字串,python不會為其建立兩個字串物件,以節省記憶體。intern機制由乙個全域性的intern字典維護。當新建乙個字串時,執行時會在intern字典中查詢是否已經存在這個字串,如果存在,則直接增加字典裡的字串物件的引用計數;如果不存在,則將其新增到字典中。

interned定義

void

pystring_interninplace(pyobject **p)

}// 在interned字典中已存在, 修改, 返回intern獨享

t = pydict_getitem(interned, (pyobject *)s);

if (t)

// 在interned字典中不存在, 放進去

if (pydict_setitem(interned, (pyobject *)s, (pyobject *)s) < 0)

// 加入interned字典(key-value), 會導致refcnt+2, 去掉, 保證當外部沒有引用時, refcnt=0, 可以進行**處理, (不-2, refcnt永遠》=2)

/* the two references in interned are not counted by refcnt.

the string deallocator will take care of this */

py_refcnt(s) -= 2;

// 修改字串物件的ob_sstate標誌位, sstate_interned_mortal

pystring_check_interned(s) = sstate_interned_mortal;

}

str使用的地方

// 構造方法

pyapi_func(pyobject *) pystring_fromstring(const

char *);

pyapi_func(pyobject *) pystring_fromstringandsize(const

char *, py_ssize_t);

// sstate_interned_mortal, 計數0會被**

pyobject *

pystring_internfromstring(const

char *cp)

// sstate_interned_immortal, 永遠不會被銷毀

void

pystring_internimmortal(pyobject **p)

示例

>>> a = ''

>>> b = ''

>>> id(a) == id(b)

true

>>> a = 'x'

>>> b = 'x'

>>> id(a) == id(b)

true

>>> a = "abc"

>>> b = "abc"

>>> id(a) == id(b)

true

好處

一旦字串被intern, 會被python儲存到字典中, 整個python執行期間, 系統中有且僅有乙個物件. 下次相同字串再進入, 不會重複建立物件.

字串拼接有兩種方式:

「+」操作符和str.join(iterable)函式。

『a』 + 『b』 + 『c』

or 」.join([『a』, 『b』, 『c』])

「+」操作符會呼叫string_concat函式。它會重新申請一塊記憶體,然後將兩個字串複製到新的記憶體中,返回乙個字串物件。也就是說,n個字串拼接會呼叫n-1次string_concat函式,即反覆申請n-1次記憶體,效率是非常底下的。

string_concat:

一次』+』=分配一次記憶體空間, 拷貝兩次. n次鏈結, 需要n-1次記憶體分配.

string_join:所以官方推薦str.join(iterable)函式,它接受乙個list或tuple等可迭代物件,統計有多少個字串,統一分配一次記憶體,然後複製左右字串到新記憶體中,並返回字串物件。可見後者效率要高很多

計算序列所有元素字串總長度, 用

pystring_fromstringandsize((char*)null, sz)分配記憶體空間, 然後逐一拷貝. 一次記憶體操作.

python原始碼剖析 Python原始碼剖析

第頁共 頁python 原始碼剖析 物件機制 1.物件 在python 的世界中,一切都是物件,乙個整數是乙個物件,乙個字串也是 乙個物件,更為奇妙的是,型別也是乙個物件,整數型別是乙個物件,字串類 型也是乙個物件。從 年guido 在那個聖誕節揭開 python 世界的大幕開始,一直到現在,pyt...

原始碼剖析 Hashtable 原始碼剖析

hashtable同樣是基於雜湊表實現的,同樣每個元素都是key value對,其內部也是通過單鏈表解決衝突問題,容量不足 超過了閾值 時,同樣會自動增長。hashtable也是jdk1.0引入的類,是執行緒安全的,能用於多執行緒環境中。hashtable同樣實現了serializable介面,它支...

原始碼剖析之CopyOnWriteArrayList

copyonwritearraylist jdk1.5新增的執行緒安全的arraylist實現。b 使用場景 讀取頻繁,寫較少。理由 底層的安全性 本質上是依賴於執行緒讀取的資料副本來實現的。因此每次寫都是要複製底層陣列資料的,如果寫頻繁勢必會造成大量的效能消耗。如果寫非常頻繁,那麼可以根據實際情況...