《Python源码剖析》读书笔记-3 字符串对象


第3章 Python中的字符串对象

  • 字符串对象定义为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;
    int ob_sstate;
    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.
    * ob_sstate != 0 iff the string object is in stringobject.c's
    * 'interned' dictionary; in this case the two references
    * from 'interned' to this object are *not counted* in ob_refcnt.
    */

    } PyStringObject;
    • 其中PyObject_VAR_HEAD宏中有一个ob_size域,表示可变长度对象的长度。
    • ob_sval实际上保存了一个指向具体字符串存储位置的指针。
  • ###Intern机制

    • Intern机制是一种缓存机制,避免了字符串在内存中占用过多的内存;同时对单字符串做出优化,使之不需要每次使用都向内存申请分配内存。
    • 首先看PyString_InternInPlace(PyObject **p),其中通过一个Dict保存所有需要通过Intern机制来共享的字符串(键值都是该字符串对象的指针)。
    • 其次,在nullstring和characters里面也保存了指向空字符对象和单字符对象的指针。当目前需要创建的字符串对象,已经在nullstring中或者characters中时,就直接取出而不会重新申请内存。而对于那些长度大于1的字符串,Python还是会为它们申请/释放内存,只是如果字符串已经被Intern,Intern机制能够保证这些字符串只占用1份内存。
    • 另外,虽然interned字典在插入时,是使用的(PyObject*)指针作为key和value,但是实际上插入和查找时都会用到string的hash值(具体如何使用,Python中的Dict对象会讲)。这样,不同时刻生成的同一个python字符串,虽然可能他们的指针不同,但是hash值相同,因此还是可以在interned字典中查找到,从而起到节省内存的作用。
    • Intern的引用计数是一个值得关注的问题。在PyString_InternInPlace(PyObject **p)存在这么一段关于引用计数的处理:

      1
      2
      3
      4
      5
      6
      7
      if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {
      PyErr_Clear();
      return;
      }
      /* The two references in interned are not counted by refcnt.
      The string deallocator will take care of this */

      Py_REFCNT(s) -= 2;
    • 如果s被插入Interned字典中,在PyDict_SetItem函数内部,会为key和value各增加一次引用计数;Py_REFCNT(s) -= 2;则将引用计数复原。这是因为Python不希望interned的字符串永远存在于内存中,所以会强制设置interned内部的引用计数无效,而当外部的引用计数为0时,就销毁该对象。在string_dealloc中做了相关的处理,使得外部引用计数为0的interned字符串对象可以从interned字典中移出。

  • ###String的连接

    • ‘+’对应了string_concat函数
    • 对于输入的两个字符串,一般的,会进行一次内存申请和两次memcpy。
    • 而join函数对应的string_join函数
    • 无论list的长度是多少,都只申请一次内存,而做2n-1次memcpy,因此对于大量的字符串连接,使用join函数更高效

评论