1.PyListObject对象的定义
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item; //指向存储列表对象指针数组的首地址
int allocated; //列表可以容纳的元素数目,注意这个和ob_size不同。ob_size表示已有元素数目,allocated表示能容纳的最多元素数目
} PyListObject;
0 <= ob_size <= allocated
len(list) == ob_size
ob_item == NULL implies ob_size == allocated == 0
这里ob_size和allocated的关系就像C++的vector中size和capacity的关系一样。
//Python中只提供了唯一一种创建PyListObject对象的方法—PyList_New:
[listobject.c]
PyObject* PyList_New(int size)
{
PyListObject *op;
size_t nbytes;
nbytes = size * sizeof(PyObject *);
/* Check for overflow */
if (nbytes / sizeof(PyObject *) != (size_t)size)
return PyErr_NoMemory();
//为PyListObject申请空间
if (num_free_lists) {
//使用缓冲池
num_free_lists--;
op = free_lists[num_free_lists];
_Py_NewReference((PyObject *)op);
} else {
//缓冲池中没有可用的对象,创建对象
op = PyObject_GC_New(PyListObject, &PyList_Type);
}
//为PyListObject对象中维护的元素列表申请空间
if (size <= 0)
op->ob_item = NULL;
else {
op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
memset(op->ob_item, 0, nbytes);
}
op->ob_size = size;
op->allocated = size;
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
创建过程大致是:
1.检查size参数是否有效,如果小于0,直接返回NULL,创建失败
2. 检查size参数是否超出Python所能接受的大小,如果大于PY_SIZE_MAX(64位机器为8字节,在32位机器为4字节),内存溢出。
3. 检查缓冲池free_list是否有可用的对象,有则直接从缓冲池中使用,没有则创建新的PyListObject,分配内存。
4. 初始化ob_item中的元素的值为Null
5. 设置PyListObject的allocated和ob_size。
说明:PyListObject和PyStringObject一样,都是变长对象,因此都有头PyObject_VAR_HEAD, 不同点是,PyStringObject是不可变对象,一旦创建,字符串内容不可改变;PyListObject则不同,可以再运行过程中动态删除、修改或新增元素。注意ob_size和allocated的关系:0 <= ob_size <= allocated
2.内存管理策略
每次申请内存是,PyListObject对象会申请一大块内存,而不是有多少元素申请多少元素,申请的总大小存储在allocated中。这样做是为了提高效率
3.修改、插入及删除操作
修改操作最简单,直接定位到相应位置替换掉对应位置的对象指针即可。如a = 100, ls[2] = a, 实际操作:(ls->ob_item + 2) = &a
插入操作:
对应两种操作,ls.insert(3, ‘new value’); ls.append(‘new value’),这两个函数操作流程几乎相同
step1: 检查ls->ob_size和ls->allocated的关系,判断是否需要重新分配内存,如果ob_size < allocated,则无需重新分配内存,转step3,否则转step2重新分配内存(这一步并不完全,实际上还会检查如果allocated> 2*ob_size,也会重新分配内存,目的是释放过多的空闲内存)
step2:重新分配内存, 注意这步需要重新申请内存空间,然后将元ob_item的数据拷贝到新申请的空间
step3:在相应位置插入元素,其后所有元素需要往后移动.(这和C++中的Vector类似,插入操作效率较低)
删除操作:
删除操作将被删除对象对应的指针删除之后,其后的所有指针都要向前移动,和C++中的Vector类似,删除效率较低
注意:
删除操作可以删除单个元素,也可以删除片段,如del ls[1]; del ls[1 : 3]
替换也可以替换一个片段, 如ls[2:4] = [1,3,4,5],这个操作实际上是通过内存拷贝来实现
4.PyListObject缓冲池
Python系统会为List对象维护一个默认大小为80的对象缓冲池,每次创建新的List对象时,系统会坚持缓冲池中有没有空余空间,若有,则直接使用,重新围棋分配ob_item的空间,否则重新创建List对象。
/* Empty list reuse scheme to save calls to malloc and free */
#define MAXFREELISTS 80
static PyListObject *free_lists[MAXFREELISTS];
static int num_free_lists = 0;
当PyListObject对象被销毁的时候,首先将列表中所有元素的引用计数减一,然后释放ob_item占用的内存,只要缓冲池空间还没满,那么就把该PyListObject加入到缓冲池中(此时PyListObject占用的内存并不会正真正回收给系统,下次创建PyListObject优先从缓冲池中获取PyListObject),否则释放PyListObject对象的内存空间。
该缓冲池初大小为0,在销毁List对象时会被分配空间。具体如下:
当销毁某List对象时,实际上分两步。第一步先销毁List维护的元素空间,即ob_item指向的内存空间,然后销毁List本身。当销毁完ob_item对应的空间之后,系统会检查缓冲池大小是否小于80,若小于80,系统不会真正销毁List对象本身,而是将其挂载到List缓冲区等待下次使用。