c++ list 修改_Cpython源码阅读17-list自动扩容原理

list是python的高级特性,今天我们来学习一下list的底层结构。list(列表)就像一个大仓库,里边什么都可以放,学习过底层结构之后,就会认同这句话。python中一切皆对象,先点出来,列表中存放的元素其实是泛型指针PyObject*,所以什么都可以放。根据我们列表的直观感受,每个列表中的元素个数可能会不一样,所以列表示一个变长对象;列表中的元素可以进行添加、删除、修改等操作,所以列表是一个可变对象。

9fa8830a7ebd0496f5d409348adb7d32.png

列表常用的操作

其中,需要注意的是性能问题,pop从尾部弹出一个元素,时间复杂度为O(1);从头部弹出一个元素,后面的元素都需要移动,时间复杂度为O(n)。

先看一下List 对象的底层结构,PyListObject,通过前面的学习,这个很简单了。

typedef struct {    PyObject_VAR_HEAD//变长对象公共头部信息    PyObject **ob_item;//二级指针,指向一个PyObject*类型的指针数组,     //这个指针数组保存的是对象指针    Py_ssize_t allocated;//列表底层使用C数组,列表的容量,非实际大小} PyListObject;

通过前面的学习,我们知道一个变长对象是通过ob_size字段来指示对象的实际大小的。列表多了一个字段allocated来表示容量,之所以要有容量这个概念,是因为列表需要动态添加元素。在动态添加元素时,如果每添加一个元素,就申请一次数组,将所有元素都拷贝一次,性能太低。加了容量这个字段,添加新元素,发现底层数组已满,会将底层数组申请的长一些,添加元素的时候不用每次都申请新的数组。

1989450d42cbd8f6c92ad4b122ceb6c0.png

PyListObject示意图

可以看出,此时列表中有两个元素,但是列表的容量是6。我们再append两个元素,如下示意图,可以看出容量最大为6,不需要重新申请数组。

107a06d00e70930dc11efa9f682b7bc5.png

尾部添加元素示意图

在此基础上再增加三个元素在尾部,实际元素个数为7,容量为6,需要扩容,看一下示意图:原来的容量为6,元素个数为4,append3个元素后,假设容量为10,元素个数为7。扩容的时候把容量申请的比实际元素大一些。然后将原来数组中的PyObject*按照顺序依次拷贝到新的数组里边,再将ob_item指向新的数组,再将ob_size加3。这就是列表扩容的过程。看一下实际源码

7a0c70b98b0450ae678d0ad39f7b7a89.png

list对象扩容底层示意图

//代码位置cpython-masterObjectslistobject.clist_resize(PyListObject *self, Py_ssize_t newsize)//参数self就是列表,newsize是元素添加或者减少之后的ob_size{    PyObject **items;//这个二级指针,用来指向指针数组    size_t new_allocated, num_allocated_bytes;//新的容量和对应的内存大小    Py_ssize_t allocated = self->allocated;//获取原来的容量  //如果newsize达到了总量的一半,但是还没有超过容量,说明newsize和容量的匹配的,不会扩容    if (allocated >= newsize && newsize >= (allocated >> 1)) {        assert(self->ob_item != NULL || newsize == 0);        Py_SET_SIZE(self, newsize);        return 0;    }//走到这里说明容量和ob_size不匹配,要进行扩容或者缩容  //新申请的底层数组的容量由下边的公式决定    new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3; //也不能过度分配    if (newsize - Py_SIZE(self) > (Py_ssize_t)(new_allocated - newsize))        new_allocated = ((size_t)newsize + 3) & ~(size_t)3;  //如果newsize为0,那么容量也会清空    if (newsize == 0)        new_allocated = 0;   //数组中存放的是PyObject*,所以要计算内存    num_allocated_bytes = new_allocated * sizeof(PyObject *);    //申请相应大小的内存,赋值给指针items    items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes);  //申请失败    if (items == NULL) {        PyErr_NoMemory();        return -1;    }  //指向新的数组,实现扩容或者缩容    self->ob_item = items;  //元素的实际个数    Py_SET_SIZE(self, newsize);  //原来容量的大小设置为新的大小    self->allocated = new_allocated;    return 0;}

new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;

从这个公式的注释得到扩容的规律:The growth pattern is: 0, 4, 8, 16, 24, 32, 40, 52, 64, 76, ...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值