一、C#的内存回收机制
实例化对象时,会在内存中分配空间,删除对象时,只是断开了该对象对内存空间的引用,实际上内存还是被占用的。当我们需要内存时,仍会不断分配内存空间,直到内存中无处可用,才会触发GC。
二、缓存池
1、什么是缓存池
缓存池(Object Pool,对象池)是一种内存 / 对象管理设计模式,核心是提前创建一批常用、创建 / 销毁成本高的对象(如游戏中的子弹、敌人,或程序中的网络连接、线程),将其存入一个 "池" 中;当需要使用时直接从池里获取,使用完毕后不销毁,而是放回池中供后续复用,避免频繁创建和销毁对象的开销。
2、缓存池的作用
-
降低性能开销
频繁创建 / 销毁对象(尤其是复杂对象,如包含大量组件的游戏物体、需要初始化资源的网络连接)会触发频繁的内存分配与回收(如 Unity 中的 GC 垃圾回收),导致性能波动(如游戏卡顿)。缓存池通过 "复用对象" 减少创建 / 销毁操作,显著降低内存碎片和 GC 压力。
-
稳定程序运行效率
避免 "峰值性能消耗":例如游戏中瞬间生成大量子弹时,若每次都
new
创建,会导致短时间内性能骤降;而从缓存池直接取对象,能保持性能稳定,响应更快。 -
统一管理对象生命周期
缓存池集中管理对象的 "创建、复用、回收",可统一控制对象的初始化状态(如重置子弹位置、清空数据),避免对象状态混乱;同时便于监控对象数量,防止资源泄漏或过度占用内存(如设置池的最大容量)。
3、典型应用场景
游戏开发:子弹、敌人、特效、UI 弹窗等高频创建 / 销毁的对象;
网络编程:数据库连接池、HTTP 连接池(复用连接,避免频繁建立 TCP 连接);
多线程:线程池(复用线程,避免频繁创建线程的系统开销)。
三、实现一个基础的缓存池
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 缓存池模块
/// </summary>
public class PoolMgr :BaseManager<PoolMgr>
{
//缓存池容器
public Dictionary<string, List<GameObject>> poolDic = new Dictionary<string, List<GameObject>>();
/// <summary>
/// 往外拿东西,出池子
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public GameObject GetObj(string name)
{
GameObject obj = null;
if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)//有抽屉,抽屉里还有东西
{
obj = poolDic[name][0];
poolDic[name].RemoveAt(0);
}
else
{
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
obj.name = name;//把对象名字改成和池子一样的名字
}
obj.SetActive(true);
return obj;
}
/// <summary>
/// 还回暂时不用的东西,进池子
/// </summary>
public void PushObj(string name,GameObject obj)
{
obj.SetActive(false);
if(poolDic.ContainsKey (name))//有抽屉
{
poolDic[name].Add(obj);
}
else
{
poolDic.Add (name,new List<GameObject>{ obj});
}
}
}
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DelayPush : MonoBehaviour
{
// Start is called before the first frame update
void OnEnable()
{
Invoke("Push", 1);
}
public void Push()
{
PoolMgr.GetInstance().PushObj(this.gameObject.name, this.gameObject);
}
}
测试脚本:
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test2 : MonoBehaviour
{
void Update()
{
if(Input.GetMouseButtonDown (0))
{
PoolMgr.GetInstance().GetObj("Cube");
}
if (Input.GetMouseButtonDown(1))
{
PoolMgr.GetInstance().GetObj("Sphere");
}
}
}
四、优化缓存池
由于三中的方法创建出的对象会直接暴露在层级窗口中,会显得没有条理,进行缓存池优化。
1、设置父对象。
2、清空缓存池。
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 池子中的一列容器
/// </summary>
public class PoolData
{
public GameObject fatherObj;//抽屉中对象的父节点
public List<GameObject> poolList;//对象的容器
public PoolData(GameObject obj,GameObject poolObj)
{
//给抽屉创建父对象,这个父对象是衣柜的子对象
fatherObj = new GameObject(obj.name);
fatherObj.transform.parent = obj.transform;
poolList = new List<GameObject>() {};
PushObj(obj);
}
public void PushObj(GameObject obj)
{
poolList.Add(obj);
obj.transform.parent = fatherObj.transform;
obj.SetActive(false);
}
public GameObject GetObj()
{
GameObject obj = null;
obj = poolList[0];
poolList.RemoveAt(0);
obj.SetActive(true);
obj.transform.parent = null;//断开父子关系
return obj;
}
}
/// <summary>
/// 缓存池模块
/// </summary>
public class PoolMgr :BaseManager<PoolMgr>
{
//缓存池容器
public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
private GameObject poolObj;
/// <summary>
/// 往外拿东西,出池子
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public GameObject GetObj(string name)
{
GameObject obj = null;
if (poolDic.ContainsKey(name) && poolDic[name].poolList. Count > 0)//有抽屉,抽屉里还有东西
{
obj = poolDic[name].GetObj();
}
else
{
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
obj.name = name;//把对象名字改成和池子一样的名字
}
obj.SetActive(true);
obj.transform.parent = null;//断开父子关系
return obj;
}
/// <summary>
/// 还回暂时不用的东西,进池子
/// </summary>
public void PushObj(string name,GameObject obj)
{
if (poolObj == null)
poolObj = new GameObject("Pool");
obj.transform.parent = poolObj.transform;//设置父对象为根节点
obj.SetActive(false);
if(poolDic.ContainsKey (name))//有抽屉
{
poolDic[name].PushObj(obj);
}
else
{
poolDic.Add (name,new PoolData(obj,poolObj));
}
}
public void Clear()
{
//防止过场景移除后会出现错误
//过场景时poolObj会被移除,但内存上还存在引用,此时就会发生错误
poolDic.Clear();
poolObj = null;
}
}