Python源碼學習筆記 3 字符串對象

1.PyStringObject

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash; //存儲字符串hash值,初始為-1
    int ob_sstate; //表明該字符串是否經過intend處理
    char ob_sval[1];//實際存儲字符串位置,默認為一個字符數組,但創(chuàng)建時會根據size大小擴展此空間
} PyStringObject;

PyString_Type:
類型對象

[stringobject.c]
PyTypeObject PyString_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "str",
    sizeof(PyStringObject),
    sizeof(char), //tp_itemsize指明變長對象保存的單位長度為一個char
    ……
    (reprfunc)string_repr,          /* tp_repr */
    &string_as_number,               /* tp_as_number */
    &string_as_sequence,            /* tp_as_sequence */
    &string_as_mapping,             /* tp_as_mapping */
    (hashfunc)string_hash,          /* tp_hash */
    0,                  /* tp_call */
    ……
    string_new,             /* tp_new */
    PyObject_Del,                       /* tp_free */
};
 

2.PyStringObject的創(chuàng)建

PyString_FromString:

 [stringobject.c]
PyObject* PyString_FromString(const char *str)
{
    register size_t size;
    register PyStringObject *op;

    size = strlen(str);  //計算size
    if (size > PY_SSIZE_T_MAX) {   //判斷字符串長度是否超過范圍
        return NULL;
    }
    
    if (size == 0 && (op = nullstring) != NULL) { //nullstring對象可能未創(chuàng)建
        return (PyObject *)op;
    }

    if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { //判斷單字符是否在緩沖區(qū)
        return (PyObject *)op;
    }

   /*創(chuàng)建字符串對象,并初始化*/
    op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
      //根據size大小調整空間
    PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    memcpy(op->ob_sval, str, size+1);
    ……
    return (PyObject *) op;
}
字符串對象內存分配

PyString_FromStringAndSize:
和FromString極為相似,只不過需要自行輸入size值,則結尾可以不是NULL

 [stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    register PyStringObject *op;
    //處理null string
    if(size == 0 && (op = nullstring) != NULL) {
        return (PyObject *)op;
    }
    //處理字符
    if(size == 1 && str != NULL && (op = characters[*str & UCHAR_MAX]) !=
       NULL)
    {
        return (PyObject *)op;
    }
    //創(chuàng)建新的PyStringObject對象,并初始化
    //Inline PyObject_NewVar
    op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
    PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    if (str != NULL)
        memcpy(op->ob_sval, str, size);
    op->ob_sval[size] = '\0';
    ……
    return (PyObject *) op;
}

3.字符串對象的刪除

static void string_dealloc(PyObject *op)
{
    switch (PyString_CHECK_INTERNED(op)) {
        case SSTATE_NOT_INTERNED:
            break;

        case SSTATE_INTERNED_MORTAL:
            /* revive dead object temporarily for DelItem */
            op->ob_refcnt = 3; //在intend字典中該對象引用了兩次,所以需要提前調整引用為3
            if (PyDict_DelItem(interned, op) != 0)
                Py_FatalError(
                    "deletion of interned string failed");
            break;

        case SSTATE_INTERNED_IMMORTAL: 
        //此狀態(tài)可通過 PyString_InternImmortal(PyObject **p)修改
            Py_FatalError("Immortal interned string died.");

        default:
            Py_FatalError("Inconsistent interned string state.");
    }
    op->ob_type->tp_free(op);
}

4.Interned實現(xiàn)

interned機制用以避免相同字符串進行多次創(chuàng)建,僅生成一個對象,節(jié)省內存同時使字符串對象做比較時僅比較對象地址是否相同即可,提升了比較效率。實現(xiàn)上通過內部的PyDictObject來存儲該字符串對象,特別注意的是存入字典時,引用計數的細微調整及其緣由。

[stringobjec.c]
void PyString_InternInPlace(PyObject **p)
{
    register PyStringObject *s = (PyStringObject *)(*p);
    PyObject *t;
    if (!PyString_CheckExact(s)) //檢查是否為PyStringObject
        return;
    if (PyString_CHECK_INTERNED(s)) 
        return;
    if (interned == NULL) { //若interned為NULL則初始化一個字典對象
        interned = PyDict_New();
    }
   /* 分析當前字符串對象是否在interned字典中(key),如果存在則增加字典中相同對象的引用,
   減少當前字符對象引用*/
    t = PyDict_GetItem(interned, (PyObject *)s);
    if (t) {
        Py_INCREF(t);
        Py_DECREF(*p);
        *p = t;
        return;
    }
    /*若不在interned字典中則增加至其中*/
    PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s);
    s->ob_refcnt -= 2; //因為加入到interned字典中時對該對象多引用了2次,需要減少
    PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; //修改Interned狀態(tài)
}
 

5.字符串緩沖池

對于單字符情況,python中維護一個PyStringObject * 的數組,在字符創(chuàng)建時會先判定是否在該數組中,如果不在則初始化到該數組中,以便日后使用。雖然Interned提供了相同字符的緩存機制,但每次仍需要針對該字符串進行malloc操作,而對單字符則可直接返回該對象地址,提升效率
緩沖池的聲明:
UCHAR_MAX 該常量和平臺相關

[stringobject.c]
static PyStringObject *characters[UCHAR_MAX + 1]; 

字符創(chuàng)建時使用緩沖池:

[stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    register PyStringObject *op;
    ……
    if(size == 1 && str != NULL && (op = characters[*str & UCHAR_MAX]) != NULL)
    {
        return (PyObject *)op;
    }
    ……
}

緩沖池元素的初始化:

緩沖池元素初始化

[stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    ……
    else if (size == 1 && str != NULL) 
    {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t); //進行interned
        op = (PyStringObject *)t;
        characters[*str & UCHAR_MAX] = op; //賦值進緩沖池
        Py_INCREF(op); //引用計數增加,避免該緩沖被刪除
    }
    return (PyObject *) op;
}

6.字符串連接的性能問題

由于字符串對象是不可變對象,所以對字符串進行普通的連接操作(+)時,會迭代的生成很多字符串對象,當待連接字符串數目龐大時,將嚴重影響性能。解決方法是使用字符串對象的join操作,join操作僅需分配一次內存,將序列中的item一次按照分割方法拼湊成一個字符串。
string_concat函數

[stringobject.c]
static PyObject* string_concat(register PyStringObject *a, register
   PyObject *bb)
{
    register unsigned int size;
    register PyStringObject *op;
    #define b ((PyStringObject *)bb)
    ……
    //計算字符串連接后的長度size
    size = a->ob_size + b->ob_size;
    
/* Inline PyObject_NewVar */
    op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
    PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    
//將a和b中的字符拷貝到新創(chuàng)建的PyStringObject中
    memcpy(op->ob_sval, a->ob_sval, (int) a->ob_size);
    memcpy(op->ob_sval + a->ob_size, b->ob_sval, (int) b->ob_size);
    op->ob_sval[size] = '\0';
    return (PyObject *) op;
#undef b
}
 

string_join函數


 [stringobject.c]
static PyObject* string_join(PyStringObject *self, PyObject *orig)
{
    char *sep = PyString_AS_STRING(self);
    //假設調用”abc”.join(list),那么self就是“abc”對應的PyStringObject對象
    //所以seplen中存儲這“abc”的長度
    const int seplen = PyString_GET_SIZE(self);
    PyObject *res = NULL;
    char *p;
    int seqlen = 0;
    size_t sz = 0;
    int i;
    PyObject *seq, *item;
    ……//獲得list中PyStringObject對象的個數,保存在seqlen中

    //遍歷list中每一個字符串,累加獲得連接list中所有字符串后的長度
    for (i = 0; i < seqlen; i++) 
    {
        //seq為Python中的list對象,這里獲得其中第i個字符串
        item = PySequence_Fast_GET_ITEM(seq, i);
        sz += PyString_GET_SIZE(item);
        if (i != 0)
            sz += seplen;
    }
    //創(chuàng)建長度為sz的PyStringObject對象
    res = PyString_FromStringAndSize((char*)NULL, (int)sz);
    //將list中的字符串拷貝到新創(chuàng)建的PyStringObject對象中
    p = PyString_AS_STRING(res);
    for (i = 0; i < seqlen; ++i) 
    {
        size_t n;
        item = PySequence_Fast_GET_ITEM(seq, i);
        n = PyString_GET_SIZE(item);
        memcpy(p, PyString_AS_STRING(item), n);
        p += n;
        if (i < seqlen - 1) 
          {
            memcpy(p, sep, seplen);
            p += seplen;
        }
    }
    return res;
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,597評論 0 6
  • 一、基礎知識:1、JVM、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機...
    殺小賊閱讀 2,553評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,511評論 19 139
  • 第六章:數字 第七章:序列,列表,元組 1.序列 序列類型有著相同的訪問模式:它的每一個元素可以通過指定一個偏移量...
    m風滿樓閱讀 977評論 0 2
  • 如果把人生的奮斗經歷打個比方,就像一場城市自行車比賽。 有的人自行車好,裝備好,速度快點,有的人自行車差...
    曾經菜如狗閱讀 826評論 2 2

友情鏈接更多精彩內容