list是python的高级特性,今天我们来学习一下list的底层结构。list(列表)就像一个大仓库,里边什么都可以放,学习过底层结构之后,就会认同这句话。python中一切皆对象,先点出来,列表中存放的元素其实是泛型指针PyObject*,所以什么都可以放。根据我们列表的直观感受,每个列表中的元素个数可能会不一样,所以列表示一个变长对象;列表中的元素可以进行添加、删除、修改等操作,所以列表是一个可变对象。
![9fa8830a7ebd0496f5d409348adb7d32.png](https://i-blog.csdnimg.cn/blog_migrate/bb1586b8b9aeded70431e931c826a4fc.jpeg)
列表常用的操作
其中,需要注意的是性能问题,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](https://i-blog.csdnimg.cn/blog_migrate/c4e0344722dbd444b25a323823491e30.jpeg)
PyListObject示意图
可以看出,此时列表中有两个元素,但是列表的容量是6。我们再append两个元素,如下示意图,可以看出容量最大为6,不需要重新申请数组。
![107a06d00e70930dc11efa9f682b7bc5.png](https://i-blog.csdnimg.cn/blog_migrate/48f558b2f18c1cede9a9b2e54d8e28d5.jpeg)
尾部添加元素示意图
在此基础上再增加三个元素在尾部,实际元素个数为7,容量为6,需要扩容,看一下示意图:原来的容量为6,元素个数为4,append3个元素后,假设容量为10,元素个数为7。扩容的时候把容量申请的比实际元素大一些。然后将原来数组中的PyObject*按照顺序依次拷贝到新的数组里边,再将ob_item指向新的数组,再将ob_size加3。这就是列表扩容的过程。看一下实际源码
![7a0c70b98b0450ae678d0ad39f7b7a89.png](https://i-blog.csdnimg.cn/blog_migrate/01a08d68341fb93cda80421ebf84cb26.jpeg)
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, ...