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;
    }
}
相关推荐
无敌最俊朗@12 小时前
C++ 序列容器深度解析:vector、deque 与 list
开发语言·数据结构·数据库·c++·qt·list
Da Da 泓12 小时前
LinkedList模拟实现
java·开发语言·数据结构·学习·算法
cxr82812 小时前
BMAD框架实践:掌握story-checklist提升用户故事质量
前端·人工智能·agi·智能体·ai赋能
emma羊羊12 小时前
【xsslabs】第12-19关
前端·javascript·靶场·xss
Humbunklung13 小时前
VC++ 使用OpenSSL创建RSA密钥PEM文件
开发语言·c++·openssl
Humbunklung13 小时前
填坑:VC++ 采用OpenSSL 3.0接口方式生成RSA密钥
开发语言·c++·rsa·openssl 3.0
zl218786544814 小时前
Playwright同步、异步、并行、串行执行效率比较
开发语言·python·测试工具
Tony Bai14 小时前
【Go开发者的数据库设计之道】05 落地篇:Go 语言四种数据访问方案深度对比
开发语言·数据库·后端·golang
gopyer15 小时前
180课时吃透Go语言游戏后端开发3:Go语言中其他常用的数据类型
开发语言·游戏·golang·游戏后端开发
come1123415 小时前
Go vs. PHP:核心优势劣势对比
开发语言·golang·php