1.创建list

Python底层是用C写的,因此列表在底层相当结构体变量,主要源码如下所示(实际上会有很多东西,后续的源码同理)。

JAVA list 源码分析 list源码解析_list


前面之所以有两个指针,是因为一个list对象相当于双向链表中的一个节点,需要前指针和后指针进行相连。

下面是创建list的源码,注意返回值是一个指针,这意味着我们定义一个列表a = [],a中实际存的是一个地址,该地址指向列表中的元素。

JAVA list 源码分析 list源码解析_python_02

2.添加元素append()

下图描述了给列表添加元素的大概过程。当我们调用append方法时,首先会在内存中创建一个空间,里面存着需要添加进列表的元素对象,之后才将该内存的地址放进列表所指向空间中(所以用来存放列表元素的是一个双指针**ob_item)。由于我们创建一个列表时,allocated实际上是等于0的,即列表的容量是0。当需要往列表append元素后,首先会判断容量够不够,够的话加就直接将添加元素的内存地址存进去,不够就扩容,按照0, 4, 8, 16 ,24的成长线进行扩容。

JAVA list 源码分析 list源码解析_list_03


扩容细节

1)首先判断列表的空间是否足够,如果空间太大,就缩容,不够才扩容。

JAVA list 源码分析 list源码解析_引用计数_04


2)开辟内存中如果存在连续的空间,就不需要迁移,如果不存在,就需要将列表中原来的元素进行迁移,与新元素进行合并。

JAVA list 源码分析 list源码解析_list_05

3.插入元素insert

插入元素需要将前面所有元素往前移动,同时计算内存空间是否充足,下图就是插入元素的一个流程。明显可以看出Insert时间复杂度为O(n),而append时间复杂度为O(1)。同理pop(0)时间复杂度为O(n),pop时间复杂度为O(1),如果需要进行insert和pop(0)可以使用双端队列deque,上面两种操作时间复杂度都为O(1)。

JAVA list 源码分析 list源码解析_python_06


insert()函数源码如下:

JAVA list 源码分析 list源码解析_引用计数_07

4.移除元素pop

移除元素是直接将把需要移除对象的引用计数减一,并将列表的size减一。列表所指向的内存空间实际未发生任何改变的。之所以这样,是因为当我们需要添加元素时,是根据obj_size的值来确定添加位置,因此只要将新元素地址直接覆盖已删除元素地址就行,这样不仅能实现功能,还能减少删除操作,提高性能。

JAVA list 源码分析 list源码解析_JAVA list 源码分析_08


pop()源码如下:

JAVA list 源码分析 list源码解析_引用计数_09

5.清空列表clear

这块没什么说的,就是将list结构体中相关变量的值重置为零,流程如下:

JAVA list 源码分析 list源码解析_list_10


clear()源码如下,注意标红的while循环,清空操作虽然将相关变量都重置为0,但需要通过这个while循环将列表所有对象的引用计数都减一,以对应垃圾回收机制。

JAVA list 源码分析 list源码解析_JAVA list 源码分析_11

6.销毁列表del

销毁一个列表对象时,会执行如下操作:
首先,将列表的引用计数器-1,此时:

1)如果引用计数器>0,表示还有其他变量使用此列表,之后不做任何操作。

2)如果引用计数器=0,表示没人使用此列表,之后需要做如下事宜。

  • 如果列表中有数据,则先处理元素,其实就是找到每个元素,将元素的引用计数器-1(如果元素的引用计数器为O,则元素可以被GC回收)
  • 处理完元素后,ob_item=NULL 、ob_size=0 、ob_allocated=0
  • 将列表从管理内存的双向环状链表中移除,认为这个对象没人使用了,现在可以被销毁了。
  • Python为了提高列表的效率,在内部设置了free_list ,它可以缓存80个列表,当有些列表没人使用后不会立即销毁,而是放在 free list中,以便再创建列表对象时可以直接从缓存中拿到列表的结构体对象并重新初始化,然后再来使用。如果free_list中已缓存了80个了,那么再有列表被销毁时,就会直接在内存中将对象销毁。下图就是对free_list 的一个验证,先删除列表,再创建列表,新建的列表和删除的列表地址相同。

    del()源码如下: