03.缓存池

一、C#的内存回收机制

实例化对象时,会在内存中分配空间,删除对象时,只是断开了该对象对内存空间的引用,实际上内存还是被占用的。当我们需要内存时,仍会不断分配内存空间,直到内存中无处可用,才会触发GC。

二、缓存池

1、什么是缓存池

缓存池(Object Pool,对象池)是一种内存 / 对象管理设计模式,核心是提前创建一批常用、创建 / 销毁成本高的对象(如游戏中的子弹、敌人,或程序中的网络连接、线程),将其存入一个 "池" 中;当需要使用时直接从池里获取,使用完毕后不销毁,而是放回池中供后续复用,避免频繁创建和销毁对象的开销。

2、缓存池的作用

  1. 降低性能开销

    频繁创建 / 销毁对象(尤其是复杂对象,如包含大量组件的游戏物体、需要初始化资源的网络连接)会触发频繁的内存分配与回收(如 Unity 中的 GC 垃圾回收),导致性能波动(如游戏卡顿)。缓存池通过 "复用对象" 减少创建 / 销毁操作,显著降低内存碎片和 GC 压力。

  2. 稳定程序运行效率

    避免 "峰值性能消耗":例如游戏中瞬间生成大量子弹时,若每次都 new 创建,会导致短时间内性能骤降;而从缓存池直接取对象,能保持性能稳定,响应更快。

  3. 统一管理对象生命周期

    缓存池集中管理对象的 "创建、复用、回收",可统一控制对象的初始化状态(如重置子弹位置、清空数据),避免对象状态混乱;同时便于监控对象数量,防止资源泄漏或过度占用内存(如设置池的最大容量)。

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;
    }
}
相关推荐
_extraordinary_6 小时前
Java 多线程(一)
java·开发语言
手握风云-6 小时前
JavaEE 进阶第四期:开启前端入门之旅(四)
前端
魔云连洲7 小时前
React中的合成事件
前端·javascript·react.js
爱喝水的鱼丶7 小时前
SAP-ABAP: ABAP ASSIGN COMPONENT 语句详解:动态字段符号的利器作用用法示例详解
运维·开发语言·sap·abap·开发经验·动态字段符号
励志不掉头发的内向程序员7 小时前
C++进阶——多态
开发语言·c++·学习
六月的可乐7 小时前
【干货推荐】AI助理前端UI组件-悬浮球组件
前端·人工智能·ui
呼啦啦呼_7 小时前
Echarts自定义地图显示区域,显示街道学校等区域,对原有区域拆分
前端
浩星7 小时前
iframe引入界面有el-date-picker日期框,点击出现闪退问题处理
前端·vue.js·elementui
技术钱7 小时前
element plus 多个form校验
前端