溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

字符串在Python內(nèi)部是怎么省內(nèi)存的

發(fā)布時間:2021-08-05 22:28:02 來源:億速云 閱讀:149 作者:chen 欄目:編程語言

本篇內(nèi)容介紹了“字符串在Python內(nèi)部是怎么省內(nèi)存的”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

起步

Python3 起,str 就采用了 Unicode 編碼(注意這里并不是 utf8 編碼,盡管 .py 文件默認編碼是 utf8 )。 每個標準 Unicode 字符占用 4 個字節(jié)。這對于內(nèi)存來說,無疑是一種浪費。

Unicode 是表示了一種字符集,而為了傳輸方便,衍生出里如 utf8 , utf16 等編碼方案來節(jié)省存儲空間。Python內(nèi)部存儲字符串也采用了類似的形式。

三種內(nèi)部表示Unicode字符串

為了減少內(nèi)存的消耗,Python使用了三種不同單位長度來表示字符串:

每個字符 1 個字節(jié)(Latin-1)每個字符 2 個字節(jié)(UCS-2)每個字符 4 個字節(jié)(UCS-4)

源碼中定義字符串結(jié)構(gòu)體:

# Include/unicodeobject.htypedef uint32_t Py_UCS4;typedef uint16_t Py_UCS2;typedef uint8_t Py_UCS1;# Include/cpython/unicodeobject.htypedef struct {  PyCompactUnicodeObject _base;  union {    void *any;    Py_UCS1 *latin1;    Py_UCS2 *ucs2;    Py_UCS4 *ucs4;  } data;           /* Canonical, smallest-form Unicode buffer */} PyUnicodeObject;

如果字符串中所有字符都在 ascii 碼范圍內(nèi),那么就可以用占用 1 個字節(jié)的 Latin-1 編碼進行存儲。而如果字符串中存在了需要占用兩個字節(jié)(比如中文字符),那么整個字符串就將采用占用 2 個字節(jié) UCS-2 編碼進行存儲。

這點可以通過 sys.getsizeof 函數(shù)外部窺探來驗證這個結(jié)論:

如圖,存儲 'zh' 所需的存儲空間比 'z' 多 1 個字節(jié), h 在這里占了 1 個字節(jié);

存儲 'z中' 所需的存儲空間比 '中' 多了 2 個字節(jié),z 在這里占了 2 個字節(jié)。

大多數(shù)的自然語言采用 2 字節(jié)的編碼就夠了。但如果有一個 1G 的 ascii 文本加載到內(nèi)存后,在文本中插入了一個 emoji 表情,那么字符串所需的空間將擴大到 4 倍,是不是很驚喜。

為什么內(nèi)部不采用 utf8 進行編碼

最受歡迎的 Unicode 編碼方案,Python內(nèi)部卻不使用它,為什么?

這里就得說下 utf8 編碼帶來的缺點。這種編碼方案每個字符的占用字節(jié)長度是變化的,這就導致了無法按所以隨機訪問單個字符,例如 string[n] (使用utf8編碼)則需要先統(tǒng)計前n個字符占用的字節(jié)長度。所以由 O(1) 變成了 O(n) ,這更無法讓人接受。

因此Python內(nèi)部采用了定長的方式存儲字符串。

字符串駐留機制

另一個節(jié)省內(nèi)存的方式就是將一些短小的字符串做成池,當程序要創(chuàng)建字符串對象前檢查池中是否有滿足的字符串。在內(nèi)部中,僅包含下劃線(_)、字母 和 數(shù)字 的長度不高過 20 的字符串才能駐留。駐留是在代碼編譯期間進行的,代碼中的如下會進行駐留檢查:

空字符串 '' 及所有;變量名;參數(shù)名;字符串常量(代碼中定義的所有字符串);字典鍵;屬性名稱;

駐留機制節(jié)省大量的重復字符串內(nèi)存。在內(nèi)部,字符串駐留池由一個全局的 dict 維護,該字段將字符串用作鍵:

void PyUnicode_InternInPlace(PyObject **p){  PyObject *s = *p;  PyObject *t;  if (s == NULL || !PyUnicode_Check(s))    return;  // 對PyUnicodeObjec進行類型和狀態(tài)檢查  if (!PyUnicode_CheckExact(s))    return;  if (PyUnicode_CHECK_INTERNED(s))    return;  // 創(chuàng)建intern機制的dict  if (interned == NULL) {    interned = PyDict_New();    if (interned == NULL) {      PyErr_Clear(); /* Don't leave an exception */      return;    }  }  // 對象是否存在于inter中  t = PyDict_SetDefault(interned, s, s);  // 存在, 調(diào)整引用計數(shù)  if (t != s) {    Py_INCREF(t);    Py_SETREF(*p, t);    return;  }  /* The two references in interned are not counted by refcnt.    The deallocator will take care of this */  Py_REFCNT(s) -= 2;  _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;}

變量 interned 就是全局存放字符串池的字典的變量名 interned = PyDict_New(),為了讓 intern 機制中的字符串不被回收,設(shè)置字典時 PyDict_SetDefault(interned, s, s); 將字符串作為鍵同時也作為值進行設(shè)置,這樣對于字符串對象的引用計數(shù)就會進行兩次 +1 操作,這樣存于字典中的對象在程序結(jié)束前永遠不會為 0,這也是 y_REFCNT(s) -= 2; 將計數(shù)減 2 的原因。

從函數(shù)參數(shù)中可以看到其實字符串對象還是被創(chuàng)建了,內(nèi)部其實始終會為字符串創(chuàng)建對象,但經(jīng)過 inter 機制檢查后,臨時創(chuàng)建的字符串會因引用計數(shù)為 0 而被銷毀,臨時變量在內(nèi)存中曇花一現(xiàn)然后迅速消失。

字符串緩沖池

除了字符串駐留池,Python 還會保存所有 ascii 碼內(nèi)的單個字符:

static PyObject *unicode_latin1[256] = {NULL};

如果字符串其實是一個字符,那么優(yōu)先從緩沖池中獲?。?/p>

[unicodeobjec.c]PyObject * PyUnicode_DecodeUTF8Stateful(const char *s,               Py_ssize_t size,               const char *errors,               Py_ssize_t *consumed){  ...  /* ASCII is equivalent to the first 128 ordinals in Unicode. */  if (size == 1 && (unsigned char)s[0] < 128) {    return get_latin1_char((unsigned char)s[0]);  }  ...}

然后再經(jīng)過 intern 機制后被保存到 intern 池中,這樣駐留池中和緩沖池中,兩者都是指向同一個字符串對象了。

嚴格來說,這個單字符緩沖池并不是省內(nèi)存的方案,因為從中取出的對象幾乎都會保存到緩沖池中,這個方案是為了減少字符串對象的創(chuàng)建。

總結(jié)

本文介紹了兩種是節(jié)省內(nèi)存的方案。一個字符串的每個字符在占用空間大小是相同的,取決于字符串中的最大字符。

短字符串會放到一個全局的字典中,該字典中的字符串成了單例模式,從而節(jié)省內(nèi)存。

“字符串在Python內(nèi)部是怎么省內(nèi)存的”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI