Python列表的底层分析
列表的内部结构
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated; 容量,即数组长度
} PyListObject;
列表有容量的概念,可以动态添加元素。但如果满了,就得申请一个更长的数组。(扩容);
列表所占内存大小计算
* PyObject_VAR_HEAD: 24字节
* ob_item: 二级指针, 8字节
* allocated: 8字节
所以为什么列表在通过索引定位元素的时候,时间复杂度是O(1);
因为列表中存储的都是对象的指针,不管对象多大,它的指针大小是固定的,都是8字节。
自动扩容
-
什么时候扩容:在添加元素时发现底层数组已经满了的情况下才会
-
容量扩容的容量和元素个数之间的规律: The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
-
扩容是指解释器发现容量不够的情况下才会扩容,如果我们直接通过lst = []这种形式创建列表的话,那么其长度和容量是一样的
列表缩容
-
如果在删除元素的时候发现长度没有超过容量但是又达到了容量一半。不会缩容
-
上面如果不到容量的一半,就会缩容
-
如果变为空列表了,容量就是0,因为Python认为,列表长度为0,说明你不想用这个列表了。
列表的常用操作
1、insert是一个时间复杂度O(n)的操作,插入位置后面的元素都要向后移动
2、pop弹出元素(通过索引,默认为-1)
3、index查询元素的索引(返回首次出现的,还可以指定start,end范围);是一个时间复杂度O(n)的操作
4、remove根据元素的值删除元素
5、判断元素是否在列表中,是一个时间复杂度O(n)的操作
列表的深浅拷贝
lst=[]
#这是浅拷贝,两个对象的地址是一样的,改变其中一个,另一个也会改变
lst_cp = lst.copy()
# 通过索引或者切片都是一样的,浅拷贝
val = lst[0]
lst_cp = lst[0:1]
因为Python中变量、容器里面的元素都是一个泛型指针PyObject *,在传递的时候回传递指针,但是在操作的时候回操作指针指向的内存。
lst.copy()就是创建了一个新列表,把元素拷贝了过去,这里的元素是指针;只是拷贝了指针,没有拷贝指针指向的对象(内存)