11_python笔记-内存管理


博客cpen_web

练习1

练习1.1

ip存储与读取文件统计ip频率 (京东二面题)
·生成一个大文件ips.txt,要求120000行,每行随即为172.25.254.0/24段的ip;
·读取ips.txt文件统计这个文件中ip出现频率前10的ip;

示例

import random

def create_file(filename:str) -> None:
    with open(filename, 'w', encoding='utf-8') as f:    # 文件操作建议使用with
        for i in range(120000):
            f.write(f'192.168.254.{random.randint(1, 255)}\n')

def counter_top(filename:str, top=10) -> list:      # 期望filename是str类型;期望return返回是一个list类型
    # key=> ip, value => 出现的次数
    result = dict()                                 # 大文件不要一次性写到内存里面,不要使用readlines
    with open(filename, mode="r", encoding="utf-8") as f:
        for ip in f:                                # 循环一行一行读。不建议使用 readlines
            ip = ip.strip()                          # 去除首尾空白字符
            if ip in result:                        # 如果ip在result里面
                result[ip] += 1                      # 次数加一
            else:
                result[ip] = 1                       # 首次出现 次数为1
    # 排序 =》
    # [(key,value), (key,value)]
    return sorted(result.items(), key=lambda x:x[1], reverse=True)[:top]    # top取前十个
                                # lambda 匿名函数  对它进行排序

filename = "ips.txt"
create_file(filename)
result = counter_top(filename)
print(result)

练习1.2

访客:编写一个程序,提示用户输入其名字;用户作出响应后,将其名字写 入到文件 guest.txt 中。

方法1

def add_username(name):
    with open("guest.txt","a+",encoding="utf-8") as f:
        f.write(name)
        f.write("\n")

username = input("请输入用户名:")
add_username(username)

方法2

with open("guest.txt","a") as fp2:
    user = input("请输入用户名: ")
    fp2.write(user)

练习1.3

生成100个MAC地址并写入文件中,MAC地址前6位(16进制)为01-AF-3B

方法1

import random
import string                       # 生成字符类型  用string模块

#随机生成一个mac地址
def create_mac():
    mac = '01-AF-3B'
    hex_num = string.hexdigits      # 随机生成一个16进制的数
    for i in range(3):
        n = random.sample(hex_num,2)
        sn = '-' + ''.join(n).upper()
        mac += sn
    return mac

#随机生成100个mac地址
def main():
    with open('mac.txt','w') as f:
        for i in range(100):
            Mac = create_mac()
            print(Mac)
            f.write(Mac + '\n')

main()

方法2

import random
with open("mac.txt","a") as fp3:
    for i in range(100):
        a=random.randint(0,16**2)
        b=random.randint(0,16**2)
        c=random.randint(0,16**2)
        d=hex(a)
        e=hex(b)
        f=hex(c)
        mac=f"01-AF-3B-{d[2:4]}-{e[2:4]}-{f[2:4]}"'\n'
        fp3.write(mac)

知识点2 内存溢出 内存泄漏

内存溢出:out of memory。用的太多,给的太少
内存泄漏:用完无法释放

内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。此时软件或游戏就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件

在计算机科学中,内存泄漏是一种资源泄漏。发生这种情况时,不再需要的内存未被释放,计算机程序以错误的方式管理内存分配。[1] 当对象存储在内存中但不能被运行代码无法访问时,也可能发生内存泄漏。[2] 内存泄漏的症状类似于许多其他问题,通常它只能由能够访问程序源代码的程序员来诊断。

知识点3 字典value值 排序

>>> a = {"a":4,"b":5,"c":2,"d":6}
>>> a.items()
dict_items([('a', 4), ('b', 5), ('c', 2), ('d', 6)])	# 返回的是列表,列表里放元组。键值对
>>> sorted(a.items(),key=lambda x:x[1])
[('c', 2), ('a', 4), ('b', 5), ('d', 6)]
>>> sorted(a.items(),key=lambda x:x[1],reverse=True)
[('d', 6), ('b', 5), ('a', 4), ('c', 2)]

x 是返回的列表里的每一个元素,每一个元素都是一个元组
X[1] 按照元组里面的第二个来排序 默认从小到大
reverse=True 返向排序

知识点4 python内存管理

·引用计数
·垃圾回收
·内存池机制
·深copy, 浅copy

Python内存管理 三大块
·引用计数
·垃圾回收
·内存池
Python的内存管理以引用计数为主,垃圾回收为辅。还有个内存池

知识点5 Python引用机制

Python动态类型
·对象是储存在内存中的实体。
·我们在程序中写的对象名,只是指向这一对象的引用(reference)
·引用和对象分离,是动态类型的核心
·引用可以随时指向一个新的对象(内存地址会不一样)

知识点6 python引用计数

在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。

引用计数器原理
·每个对象维护一个 ob_ref 字段,用来记录该对象当前被引用的次数 每当新的引用指向该对象时,它的引用计数ob_ref加1
·每当该对象的引用失效时计数ob_ref减1
·一旦对象的引用计数为0,该对象可以被回收,对象占用的内存空间将被释放。 它的缺点是需要额外的空间维护引用计数,这个问题是其次的
·最主要的问题是它不能解决对象的“循环引用”

·a = 1 , b = 1 ,1的引用计数为2(保存它被引用的次数)
·a = 2 , b = 3 , 1的引用计数为0(内存里面不需要它了,回收销毁,这块对象被回收了,对象占用的内存空间将被释放)
Python的内存管理以引用计数为主,垃圾回收为辅。还有个内存池

知识点7 获取引用计数: getrefcount()

获取引用计数: getrefcount()
当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,
getrefcount()所得到的结果,会比期望的多1。

示例

from sys import getrefcount		# 导入模块
a = [1,2,3]
print(getrefcount(a))   # 获取对象a的引用计数 , 结果为2
# 引用计数为2 。因为getrefcount获取引用计数时,会创建临时引用指向对象a = [1,2,3] , 拿到对象之后才能获取它的引用计数
# getrefcount创建临时引用,结束后会被回收
# a =  在内存里开辟了一份空间 存放列表对象,让 a 指向它
# 使用getrefcount获取引用计数,要 - 1
b = a
print(getrefcount(a))   # 引用计数为3 。 因为getrefcount创建临时引用,获取后就释放了(上一个getrefcount临时引用被释放了)
# 真实的引用情况是2 
c = [a,a]
print(getrefcount(a))   # 引用计数为5(4+1),列表里面存放的都是引用。4个引用 + getrefcount获取的1个临时引用
print(getrefcount(a))   # 引用计数都是5。b和a指向的对象都是同一个,打印a和b引用计数都是一样的
print(getrefcount(b))   # 引用计数都是5。b也创建一个对象  引用对象[1,2,3]
print(getrefcount(c))   # c的引用计数是2(1+1),有1个临时引用

知识点8 增加引用计数

当一个对象A被另一个对象B引用时,A的引用计数将增加1
增加引用计数 基本通过赋值

知识点9 减少引用计数

del删除或重新引用时,引用计数会变化(del只是删除引用)

示例

from sys import getrefcount
a = [1,2,3]	# 真实引用计数:1
b = a		# 真实引用计数:2
c = [a,a]		# 真实引用计数:4
del c[0]            # del删除引用	引用计数 - 1 ; 真实引用计数: 3
print(c)            # c 是列表对象	输出为 [[1, 2, 3]]
print(getrefcount(a))   # 引用计数为4,真实引用计数为3
b = 2               # 重新引用,引用计数发生改变	(引用计数 - 1)
print(getrefcount(a))   # 引用计数为3,真实引用计数为2

# 容器里面存的都是对象的引用,不是实际值

知识点10 循环引用的情况

循环引用的情况
x = []
y = []
x.append(y) y.append(x)
对于上面相互引用的情况,如果不存在其他对象对他们的引用,这两个对象所占用的内存也还是无法回收,从而导致内存泄漏.

示例1

>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> x
[1, [2]]
>>> y.append(x)
>>> y
[2, [1, [...]]]		# 注:发生死循环

示例2

>>> from sys import getrefcount
>>> x = ["x"]
>>> y = ["y"]
>>> getrefcount(x)
2
>>> getrefcount(y)
2
>>> x.append(y)
>>> getrefcount(x)
2
>>> getrefcount(y)
3
>>> y.append(x)
>>> getrefcount(x)
3
>>> x
['x', ['y', [...]]]
>>> y
['y', ['x', [...]]]
>>> del x	
>>> y
['y', ['x', [...]]]
>>> del y				# del x;del y引用删除,这块内存区域获取不到了

知识点11 引用计数机制优缺点

引用计数机制的优点:

·简单
·实时性

引用计数机制的缺点:

·维护引用计数消耗资源
·循环引用时,无法回收

知识点11 Python垃圾回收

11.1 回收原则

·当Python的某个对象的引用计数降为0时,可以被垃圾回收

11.2 gc机制

·GC作为现代编程语言的自动内存管理机制,专注于两件事
·找到内存中无用的垃圾资源
·清除这些垃圾并把内存让出来给其他对象使用。
GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在业务逻辑上。但这并不意味着码农就可以不去了解GC,毕竟多了解GC知识还是有利于我们写出更健壮的代码。

11.3 效率问题

·垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。
·当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。

示例

>>> import gc
>>> print(gc.get_threshold())
(700, 10, 10)					# 注:默认值

示例

>>> del x
>>> del y
>>> gc.collect()
2							# 删除了2个循环引用

>>> a = "x1 xx"
>>> b = "x1 xx"
>>> a = 1
>>> b = 2
>>> gc.collect()
0

11.4 三种情况触发垃圾回收

·调用gc.collect()
·GC达到阀值时
·程序退出时

11.5 Python垃圾回收 - 标记清除

标记清除
标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。
主要用于解决循环引用。
1.标记:活动(有被引用), 非活动(可被删除)
2.清除:清除所有非活动的对象

11.6 Python垃圾回收 - 分代(generation)回收

分代(generation)回收
这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。
·Python将所有的对象分为0,1,2三代。
·所有的新建对象都是0代对象。
·当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
·垃圾回收启动时,一定会扫描所有的0代对象。
·如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。
·当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。

11.7 总结

Python的内存管理:
·内存管理 分 引用计数、垃圾回收、内存池
·引用计数为主,分代回收和标记清除为辅的内存回收方式

知识点12 Python缓冲池

内存池:
·整数缓冲区 [-5,256]
·字符对象缓冲区(字符串驻留区)
数字、字母下划线组成的这个字符串
乘法的字符串最多可以驻留20个字符
注:小数没有缓冲池。只缓冲整数

知识点12.1 整数对象缓冲池

整数对象缓冲池
对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提前申请了一块内存空间,等需要的时候在这上面创建大整数对象。

示例:小整数而言 id都是一样的

>>> a = 777		# a和b不是一样的
>>> b = 777
>>> id(a)			# 内存地址不同
140133545530064
>>> id(b)			# 内存地址不同
140133545530384
>>> a = b = 777
>>> id(a)
140133545530480
>>> id(b)
140133545530480
>>> a = 1			# a和b是一样的
>>> b = 1			# python的整数对象缓冲池
>>> id(a)
140133544871840	# 内存地址一样
>>> id(b)
140133544871840	# 内存地址一样
>>> from sys import getrefcount
>>> getrefcount(a)
801

知识点12.2 字符串缓存

字符串缓存
为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同。
当触发缓存机制时,只是创造了新的引用,而不是对象本身。

示例

>>> a = "xxx"
>>> b = "xxx"
>>> id(a)
140133545760616
>>> id(b)
140133545760616
>>> a = "xxx "		# 注:特殊字符不能放到缓冲区
>>> b = "xxx "
>>> id(a)
140133545760672		# 内存地址不一样
>>> id(b)
140133545760728
>>> a = "xxx_"		# 注:数字、字母、下划线的组合 放在字符串缓冲区
>>> b = "xxx_"
>>> id(a)
140133545760616		# 内存地址一样
>>> id(b)
140133545760616
>>> a = "hello world"
>>> b = "hello world"
>>> id(a)				# 内存地址不一样
140133545242928
>>> id(b)
140133545242992
>>> a = "helloworld"	
>>> b = "helloworld"
>>> id(a)				# 内存地址一样
140133545243120
>>> id(b)
140133545243120

示例:对于乘法创建的字符 只会缓冲20个

>>> a = "x"*21
>>> b = "x"*21
>>> id(a)			# 内存地址不一样
140133545742176
>>> id(b)
140133545742248
>>> a = "x"*20	# 内存地址一样
>>> b = "x"*20
>>> id(a)
140133545246768
>>> id(b)
140133545246768

知识点12.3

注意
这对于经常使用的数字和字符串来说也是一种优化的方案

字符串的intern机制
python对于短小的,只含有字母数字的字符串自动触发缓存机制。其他情况不会缓存。

知识点13 Python深拷贝与浅拷贝

示例

>>> a=[[]]*3			# 引用的 [] 是同一个内存地址
>>> a				# 生成3个引用,指向同1个对象(浅拷贝)通常情况都是浅拷贝(可变数据类型)
[[], [], []]
>>> b=[[1]]*3			
>>> b
[[1], [1], [1]]
>>> a[0].append(1)		# 生成3个引用,指向同1个对象(浅拷贝)通常情况都是浅拷贝(可变数据类型)
>>> a
[[1], [1], [1]]
>>> b = []
>>> for i in range(3):
...     b.append([])		# 3个不同对象
... 
>>> b
[[], [], []]
>>> b[0].append(1)
>>> b
[[1], [], []]
>>> a = {"first":[1,2,3]}	# 可变类型
>>> a
{'first': [1, 2, 3]}
>>> b = a
>>> id(a)					# 内存地址相同
140133545248160
>>> id(b)
140133545248160
>>> a[2]="No.2"
>>> a["first"].append(4)
>>> a
{'first': [1, 2, 3, 4], 2: 'No.2'}
>>> b
{'first': [1, 2, 3, 4], 2: 'No.2'}

知识点13.2 浅拷贝

拷贝第一层数据(地址)

示例

>>> a = {"first":[1,2,3]}
>>> b = a.copy()			# 拷贝第一层数据(地址)
>>> a
{'first': [1, 2, 3]}
>>> b
{'first': [1, 2, 3]}
>>> id(a)					# a、b引用变了
140133410603584
>>> id(b)
140133545741768
>>> a["second"] = "No.2"
>>> a
{'first': [1, 2, 3], 'second': 'No.2'}
>>> b
{'first': [1, 2, 3]}
>>> a["first"].append(4)		# a、b里面的”first”引用 没有改变
>>> a						# 拷贝第一层数据(地址)
{'first': [1, 2, 3, 4], 'second': 'No.2'}
>>> b
{'first': [1, 2, 3, 4]}
>>> id(a["first"])				# 第一层数据(地址) 内存地址相同
140133413100296
>>> id(b["first"])
140133413100296

知识点13.3 深拷贝

递归拷贝所有层的数据

示例

>>> a = {"first":[1,2,3]}
>>> import copy				# 导入模块
>>> b = copy.deepcopy(a)
>>> id(a)						# 内存地址不同
140133545248160
>>> id(b)
140133410604736
>>> a["second"] = "No.2"
>>> a["first"].append(4)
>>> a
{'first': [1, 2, 3, 4], 'second': 'No.2'}		# 递归拷贝所有层的数据
>>> b
{'first': [1, 2, 3]}						# 递归拷贝所有层的数据

知识点13.4 小结

小结
·数字和字符串、元组,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。
·列表、字典、集合可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object)。

示例

>>> a=[1,2,3]
>>> b=[1,2,[4,5]]			# 可变数据对象,有影响
>>> c = b[:]
>>> c
[1, 2, [4, 5]]
>>> b[2].append(7)
>>> b
[1, 2, [4, 5, 7]]
>>> c
[1, 2, [4, 5, 7]]
>>> b=[1,2,3]				# 不可变数据对象,没有影响
>>> c = b[:]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mycpen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值