网站的建设与开发,html框架做网站,wordpress acf使用,网站空间那个好引用计数器
首先我们大概回忆一下C语言中的环状双向链表#xff0c;如图#xff0c;在双向链表中对于一个结点来说会有前驱和后继#xff1a; C语言中基本的定义方式如下#xff1a;
typedef struct {ElemType data; // 数据域Lnode* prior; // 前驱指针域Lnode* next;…引用计数器
首先我们大概回忆一下C语言中的环状双向链表如图在双向链表中对于一个结点来说会有前驱和后继 C语言中基本的定义方式如下
typedef struct {ElemType data; // 数据域Lnode* prior; // 前驱指针域Lnode* next; // 后继指针域
}Lnode;
现在我们把这段基础代码拿到python中来详细看看会被怎么样拓展编写
typedef struct _object {struct _object* ob_next; // 下一个元素struct _object* ob_prev; // 上一个元素Py_ssize ob_refcnt; // 引用计数器struct _typeobject* ob_type; // 数据类型 _typeobjec会根据类型替换
} PyObject;typedef struct {PyObject ob_base; // PyObject对象Py_ssize_t ob_size; // 元素个数
} PyVarObject;
在python中这个环状链表C源码的表示如上可以发现在它分为PyObject和PyVarObject两个结构体在python底层C源码中每个类型都有其对应的结构体 PyObject是结点的固定变量指向上一个的指针、指向下一个的指针、引用计数器、数据类型构成的结构体 PyVarObject是有多个元素组成的对象例如一个列表L[a,b,c]构成的结构体 我们把目光看到引用计数器首先我们要知道上面的这样一个环形双向链表在python中称为refchain双线链表当python程序执行的时候会根据不同的类型找到对应的类型再根据结构体中的字段来创建相关的数据然后将对象添加到refchain双线链表中
那我们来具体看看是怎么做的例如在python中你输入一行代码a 1.25python会帮你创建出这些内容
输入a 1.25创建
_ob_prev refchain的上一个对象
_ob_next refchain的下一个对象
ob_refcnt 1
ob_type int
ob_fval 1.25
_ob_prev 用于保存上一个对象_ob_next 用于保存下一个对象ob_refcnt 1 引用计数器ob_type int 数据类型ob_fval 1.25 数据的值
可以看到由于定义了a1.25所以计数器记录了一次当有其他值引用对象时候计数器就会发生变化
a 1.25 # ob_refcnt1
b a # ob_refcnt2del b # ob_refcnt1
del a # ob_refcnt0
当我将计数器的值减成0的时候程序中已经没有人再需要这个对象了所以这个对象也就变成了垃圾那就要进行垃圾回收程序就会将该对象完全从refchain中销毁内存也会释放出来 现在你应该开始了解垃圾回收了吧我们来回忆一下引用计数器和垃圾回收
因为程序会将我们创建的对象放到refchain环形双向链表中而该链表的每个对象的结构中都有一个引用计数器ob_refcnt当你创建的时候默认值就是1了如果这个对象已经被你删除了那就没有用即成为垃圾程序就会进行垃圾回收将该对象直接从链表中删除此时该对象被销毁他占有的内存空间也就被释放了 但是这样的引用计数器并不是万能的他会面临着循环引用和交叉感染的风险下面我们看这段代码
v1 [1, 2, 3] # v1的ob_refcnt 1
v2 [4, 5, 6] # v2的ob_refcnt 1v1.append(v2) # v2的ob_refcnt 2
v2.append(v1) # v1的ob_refcnt 2del v1 # v1的ob_refcnt 1
del v2 # v2的ob_refcnt 1
虽然我们已经删除v1v2已经不需要使用他们但是这两个变量始终无法彻底从refchain链表中完全销毁就会占用多余空间浪费资源
所以为了解决这个问题python又引入了标记清除这个方法
标记清除
目的解决引用计数器的循环引用的不足
实现在python的底层又去维护了一个新链表该链表里面专门存储这些有可能存在循环引用的对象
哪些对象可能存在循环引用呢
如果只是单纯的引用赋值等基本不会产生循环引用问题你可以删除该但是当该对象是被另一个对象嵌套使用的时候你是无法删除这个嵌套引用的操作的此时就有可能产生循环引用问题而能包含其他对象的有以下几种列表、元组、集合、字典所以我们可以将这些怀疑对象放入新链表
python会定期去扫描这个新链表就可以检查当中的元素是否有循环引用如果有计数器-1后值0那就是垃圾直接将其从refchain链表删除 但是这也有一个新问题
什么时候扫描合适呢如果一直扫描程序性能会受影响其次是扫描列表这些每一个子元素都要扫描耗时就会比较久
为了解决这些问题又有了分代回收
分代回收
为了解决标记清除的两个问题分代回收方法将存储可能存在循环引用问题的对象分为三个链表分别是0、1、2代链表
0代所有可能对象都先存储在0代链表中当0代链表打到700个数据的时候程序对该链表做一次扫描将垃圾对象在refchain中销毁剩余非垃圾对象全部移到1代链表中此时1代链表会记录下0代链表扫描了1次以此类推1代当0代链表扫描10次后1代链表扫描1次2代当1代链表扫描10此后2代链表扫描1次
这样扫描次数不会过多同样的数据也无需重复扫描检查过多次 缓存机制
基于以上几步python对其又做出了优化机制即为python缓存缓存基本分为两类池和free_list
池int...
先说第一种缓存机制池
v1 9 # 创建一个对象变量名为v1值为9
v2 8 # 创建一个对象变量名为v2值为8
v3 9 # 创建一个对象变量名为v3值为9
按理来说三个变量应该是不同的地址我们打印出来看看
print(id(v1), id(v2), id(v3))
# 140737462441000 140737462440968 140737462441000
可以发现v1和v3的值相同于是两者的地址也相同
之所以这样是因为当我们创建一些简单变量的时候为了避免反复创建销毁python提前为我们准备好了一些数据存储起来构成了一个数据池当我们创建的对象值在这个范围内程序会去池子里面找到该值直接拿来用
例如int类型的值有-5~2579早就已经被创建好放到池子里v1和v3只是找了同一个地址上的值拿来用所以两个地址也相同 str类型也会预存ascli字符
str1 A
str2 Aprint(id(str1),id(str2))
# 140737462484992 140737462484992
除此之外python还给字符串设置了驻留机制python中有一段常见代码
str3 python
str4 python
print(id(str3) id(str4)) # True 此时‘python’可不是ascil字符表的内容了之所以两者地址相同正因为这个驻留机制使得对于这些只有下划线、数字、字母的简单字符串如果在内存中已经存在那么再次创建就可以直接用原来地址而不需要在free_list中存留 free_list(turple/list/set/dict)
l [1, 2]
l1 [1, 2]
l2 [3, 4]print(id(l)) # 4336
print(id(l1)) # 9264
print(id(l2)) # 8752
当我们创建了三个列表列表之间并不会因为数据相同就有相同地址说明python并没有像池一样给我们缓存一些列表数据
del l1
del l2l3 [5, 6]
print(id(l3)) # 8752l4 [7, 8, 9]
print(id(l4)) # 9264
接着我们删除了l1和l2添加了新列表l3和l4可以看到3和2的地址相同4和1的地址相同说明3用的是2的空间4用的是1的空间也就说明1和2虽然被删除了但是还没有完全销毁他们的位置被保存下来了
这个保存他们位置的地方就是free_list像列表、元组、字典、集合这些数据类型被删除的时候并不会直接被完全销毁而是将其保存在free_list中等到有相同类型被创建的时候就会占用他们的位置将内部的值和名字都改为新的 注意元组在free_list内元组会以不同个数分别保存起来当释放对应个数的元组时会在free_list中找相应个数元组的地址
在free_list中T([1个元素的元组],[2个元素的元组],[3个元素的元组]....)t1 (1, 2)
t2 (3, 4, 5)del t1 # 将t1存放到有2个元素的元组中
del t2 # 将t2存放到有3个元素的元组中t3 (1) # 去只有1个元素的元组中找位置
t4 (8, 9) # 去只有2个元素的元组中找位置 此外该free_list并不是无限的当它满了一定数量就不会再缓存了而是照常直接销毁