simple_pool对象池——优化<二>
本文章由cartzhang编写。转载请注明出处。 所有权利保留。
文章链接:http://blog.csdn.net/cartzhang/article/details/55051570
作者:cartzhang
simple pool 博客一的地址:
http://blog.csdn.net/cartzhang/article/details/54096845
一、simpe_pool对象池的问题
在上篇对象池simple_pool中提到了它如今的问题。
一个是数据控制,也就是在单个父亲节点下,仅仅会一直增加来满足当前游戏对对象池内的对象数量的须要,没有考虑到降低。也就是说,若在A阶段,游戏场景中同一时候须要大量的某个池内O1对象,这时候就出现大量的AO1对象在内存中。可是过了A阶段,不须要A这么多O1对象时候,对象池内没有做操作和优化。
还有一个问题,就是多线程的问题。这个暂时不这里讨论。
有须要能够自己先对linkedlist加锁。
本片针对第一个问题。来做了处理。
顺便给出上篇simple_pool博客地址:
http://blog.csdn.net/cartzhang/article/details/54096845
https://github.com/cartzhang/simple_pool_bench
二、对象池内数量优化思路
本着在后台处理,尽量少影响对象池的使用的原则。决定使用在增加一个线程来实现。对须要清理的池内对象进行记录和推断。然后在主线程中进行删除(destroy)操作.
由于Unity不同意在其它自己建立的线程中调用Destroy函数,所以还是要在Update中进行处理。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2FydHpoYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">
这个是自己画的流程图,基本就是这个样子。当然里面详细參数,能够依据自己须要调整。
三、实现代码
1.首先,建立垃圾清理标记和垃圾对象列表。
// garbage clean.
private static bool clearGarbageFlag = false;
private static List<Transform> GarbageList = new List<Transform>(100);
然后标记为真。開始清理。
这里进行了加锁,避免一边删除。一边增加,这样造成野的对象。垃圾表被清空了,对象也不在对象池内列表中。
2.主线程的清理工作
private void Update()
{
CleanGarbageList();
}
private void CleanGarbageList()
{
if (clearGarbageFlag)
{
clearGarbageFlag = false;
lock (GarbageList)
{
Debug.Assert(GarbageList.Count > 0);
Debug.Log("now destroy " + GarbageList.Count + " from pool" + GarbageList[0].name);
for (int i = 0; i < GarbageList.Count; i++)
{
Destroy(GarbageList[i].gameObject);
}
GarbageList.Clear();
}
}
}
3.优化线程启动
线程在第一次调用池的时候開始启动。
然后定时检測数量,每轮会检測checkTimesForEach个对象池内的未使用的情况。对象池个数超出的部分,等待5个中的一个多次检測没有异常后,在增加到检測中,这个主要是为了防止多个对象池内,一次性增加垃圾列表中太多对象。须要一次性删掉的太多,造成主线程卡顿的情况。
当然这也不是最理想的,为了防止卡顿。每次检測循环仅仅检測到一个满足垃圾清理条件,须要处理就会停止检測跳出循环,然后进行垃圾处理。这个也是为了轻量级的删减措施。
一旦成功设置标志。就又一次计算和检測。而且在设置后,优化线程等待1秒时间,来让主线程做工作。这个时间应该是非常充裕的。
主要代码
private static void OptimizationPool()
{
// check cpu time to start
Thread.Sleep(100);
// 检測间隔时间20秒
float intervalTimeTodetect = 20f;
// after how many times counts to reset count.
// 循环检測多少次。后记录清零。
每次仅仅处理须要处理的前5个池。
const int checkTimesForEach = 5;
// 暂时池管理对象
Dictionary<int, ObjectPool> poolManagerTempDic = new Dictionary<int, ObjectPool>();
System.DateTime timeCount = System.DateTime.Now;
// 间隔时间内运行一次
bool eachMinuteGetDicOnce = false;
// 每一个池未使用对象超过一半的标记,记录次数
Dictionary<int, int> CurrentPoolUnuseCount = new Dictionary<int, int>();
// 检測刷新次数。也是一次计数的最大时间。
int icountLoopTime = 0;
Debug.Log("Thread start");
// 休眠时间
int sleepTime = 10;
while (isStartThread)
{
Thread.Sleep(sleepTime);
if (!eachMinuteGetDicOnce)
{
eachMinuteGetDicOnce = true;
poolManagerTempDic = poolManagerDic.ToDictionary(entry => entry.Key,entry => entry.Value);
// loop check 3 time to reset.
if (icountLoopTime % checkTimesForEach == 0)
{
CurrentPoolUnuseCount.Clear();
icountLoopTime = icountLoopTime > 10000000 ?
0 : icountLoopTime;
}
// mark unuse nuber for all.
foreach(var element in poolManagerTempDic)
{
ObjectPool opool = element.Value;
int unusinglinkCount = opool.UnusingLinkedList.Count;
// half of all is useless and more than 10.
if (unusinglinkCount * 2 > unusinglinkCount + opool.UsingLinkedList.Count && unusinglinkCount > 10)
{
MarkCountForUnusingLink(ref CurrentPoolUnuseCount, element.Key);
// satisfy the condition,add unusing link gameobject to garbagelist.
int currentMark = 0;
CurrentPoolUnuseCount.TryGetValue(element.Key,out currentMark);
// be marked three times, add to garbage list.
if (currentMark >= 3)
{
AddObjectsToGarbageList(ref opool.UnusingLinkedList);
// count tick to reset.
CurrentPoolUnuseCount[element.Key] = 0;
clearGarbageFlag = true;
// each time only gathing one pool to process.
break;
}
}
}
}
// leave time for mainthread to delete gameobjects.
if (clearGarbageFlag)
{
icountLoopTime = 0;
intervalTimeTodetect = 20f;
Thread.Sleep(1000);
timeCount = System.DateTime.Now;
}
// interval 20 seconds to start check once;
if ((System.DateTime.Now - timeCount).TotalSeconds > intervalTimeTodetect)
{
timeCount = System.DateTime.Now;
eachMinuteGetDicOnce = false;
poolManagerTempDic.Clear();
icountLoopTime++;
Debug.Log("Loop count is " + icountLoopTime);
}
// long time nothing happen, expand the detective interval time (max <= 90s).
if (icountLoopTime >= 4 )
{
intervalTimeTodetect = intervalTimeTodetect * 2 >= 90f ? 90f : intervalTimeTodetect * 2;
icountLoopTime = 0;
Debug.Log("interval time is " + intervalTimeTodetect);
}
}
return;
}
/// <summary>
/// add last gameobject to garbagelist,as when unsing unusinglink is from first place to get.
/// </summary>
/// <param name="list"></param>
private static void AddObjectsToGarbageList(ref LinkedList<Transform> list)
{
Debug.Assert(list.Count > 0);
int FlagDestroyNumber = list.Count>>1;
for (int i = 0; i < FlagDestroyNumber; i++)
{
GarbageList.Add(list.Last.Value);
list.RemoveLast();
}
}
依照眼下设置參数,開始检測的时间间隔为20秒,若20*2=40秒后,没有须要处理的垃圾,就把检測时间间隔翻倍为40秒检測一次;若在过40*4=160秒,没有触发标志。检測时间进一步延长,逐次翻倍增加。可是最大值为90秒。
也就是说。最大的检測间隔为90秒。
若中间被打断。所有归为正常20秒检測一次。
触发增加垃圾列表的条件:
设置触发标志是一个池,在3次检測中都有超过一半的对象没有被使用,而且总体未使用数量超过10个。
四、更新project分享地址:
地址:https://github.com/cartzhang/simple_pool_bench
能够下载Assets文件。然后用unity測试。
这个測试demo为 PoolTimeOptimizeOjbectsDemo.unity。
优化流程图下载地址:
https://github.com/cartzhang/simple_pool_bench/blob/master/img/pool_bench_optimize.png
若有问题,请提交问题或代码。非常感谢!
。!
五、附件
poolManager.cs 所有代码:
using UnityEngine;
using System.Collections.Generic;
using SLQJ_POOL;
using UnityEngine.Internal;
using System.Threading;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
/// <summary>
/// 扩展GameObject函数
/// 使用扩展函数方面代码调用,能够Unity的随意代码中,调用对象池来产生和回池对象,
/// 而且不须要调用命名空间等繁琐东西。
/// 代码样例: obj = gameObject.InstantiateFromPool(prefab);
/// gameObject.DestroyToPool(poolObjs[poolObjs.Count - 1], 0.2f);
/// 详细能够參考HowToUse脚本和TestEffect。
/// @cartzhang
/// </summary>
public static class GameObjectExten
{
/// <summary>
/// 调用对象池,产生一对象,并带有位置和旋转等參考
/// </summary>
/// <param name="gameobject"></param>
/// <param name="original"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <returns></returns>
public static GameObject InstantiateFromPool(this GameObject gameobject, Object original, Vector3 position, Quaternion rotation)
{
return PoolManager.PullObjcetFromPool(original as GameObject, position, rotation).gameObject;
}
/// <summary>
/// 调用对象池,产生对象
/// </summary>
/// <param name="gameobject"></param>
/// <param name="original"></param>
/// <returns></returns>
public static GameObject InstantiateFromPool(this GameObject gameobject, Object original)
{
return PoolManager.PullObjcetFromPool(original as GameObject).gameObject;
}
/// <summary>
/// 对象返回对象池
/// </summary>
/// <param name="gameobject"></param>
/// <param name="obj"></param>
public static void DestroyToPool(this GameObject gameobject, Object obj)
{
PoolManager.PushObjectPool((obj as GameObject).transform);
}
/// <summary>
/// 带延时的返回对象池
/// </summary>
/// <param name="gameobject"></param>
/// <param name="obj"></param>
/// <param name="t"></param>
public static void DestroyToPool(this GameObject gameobject, Object obj, [DefaultValue("0.0F")] float t)
{
PoolManager.PushObjectPool((obj as GameObject).transform, t);
}
}
namespace SLQJ_POOL
{
public class PoolManager : MonoBehaviour
{
private static PoolManager instance;
private static bool bStartThreadOnce = false;
private static bool isStartThread = false;
private static Thread tOptimizationThread;
private static List<GameObject> prefabList = new List<GameObject>();
//存放预制体相应的id。ObjcetPool
public static Dictionary<int, ObjectPool> poolManagerDic = new Dictionary<int, ObjectPool>();
private static Dictionary<Transform, ObjectPool> transformDic = new Dictionary<Transform, ObjectPool>();
// garbage clean.
private static bool clearGarbageFlag = false;
private static List<Transform> GarbageList = new List<Transform>(100);
private void Update()
{
CleanGarbageList();
}
private void CleanGarbageList()
{
if (clearGarbageFlag)
{
clearGarbageFlag = false;
lock (GarbageList)
{
Debug.Assert(GarbageList.Count > 0);
Debug.Log("now destroy " + GarbageList.Count + " from pool" + GarbageList[0].name);
for (int i = 0; i < GarbageList.Count; i++)
{
Destroy(GarbageList[i].gameObject);
}
GarbageList.Clear();
}
}
}
//初始化某个预制体相应的对象池
public static void InitPrefab(GameObject prefab, int initNum = 4)
{
GetObjectPool(prefab, initNum);
}
//外界调用的接口
public static Transform PullObjcetFromPool(GameObject prefab)
{
return _PullObjcetFromPool(prefab);
}
public static Transform PullObjcetFromPool(GameObject prefab, Vector3 pos, Quaternion quaternion)
{
return _PullObjcetFromPool(prefab, pos, quaternion);
}
private static Transform _PullObjcetFromPool(GameObject prefab)
{
if (prefab == null)
{
Debug.Log("prefab is null!");
return null;
}
ObjectPool objPool = GetObjectPool(prefab);
StartThreadOnce();
return objPool.PullObjcetFromPool();
}
private static Transform _PullObjcetFromPool(GameObject prefab, Vector3 pos, Quaternion quaternion)
{
if (prefab == null)
{
Debug.Log("prefab is null!");
return null;
}
ObjectPool objPool = GetObjectPool(prefab, pos, quaternion);
StartThreadOnce();
return objPool.PullObjcetFromPool(pos, quaternion);
}
private static ObjectPool GetObjectPool(GameObject prefab, int initNum = 4)
{
ObjectPool objPool = null;
//推断集合中是否有预制体相应的对象池
int leng = prefabList.Count;
int prefabID = prefab.GetInstanceID();
for (int i = 0; i < leng; i++)
{
if (prefabID == prefabList[i].GetInstanceID())
{
objPool = poolManagerDic[prefabID];
break;
}
}
//没有找到对象池的话创建一个对象池
if (objPool == null)
{
objPool = CreatObjcetPool(prefab, initNum);
}
return objPool;
}
private static ObjectPool GetObjectPool(GameObject prefab, Vector3 pos, Quaternion qua, int initNum = 4)
{
ObjectPool objPool = null;
int leng = prefabList.Count;
int prefabID = prefab.GetInstanceID();
for (int i = 0; i < leng; i++)
{
if (prefabID == prefabList[i].GetInstanceID())
{
objPool = poolManagerDic[prefabID];
}
}
if (objPool == null)
{
objPool = CreatObjcetPool(prefab, pos, qua, initNum);
}
return objPool;
}
private static ObjectPool CreatObjcetPool(GameObject prefab, Vector3 pos, Quaternion qua, int initNum)
{
prefabList.Add(prefab);
GameObject go = new GameObject();
go.name = prefab.name + "Pool";
ObjectPool objPool = go.AddComponent<ObjectPool>();
objPool.InitObjectPool(prefab, pos, qua, transformDic, initNum);
poolManagerDic.Add(prefab.GetInstanceID(), objPool);
return objPool;
}
private static ObjectPool CreatObjcetPool(GameObject prefab, int initNum)
{
prefabList.Add(prefab);
GameObject go = new GameObject();
go.name = prefab.name + "Pool";
ObjectPool objPool = go.AddComponent<ObjectPool>();
objPool.InitObjectPool(prefab, transformDic, initNum);
poolManagerDic.Add(prefab.GetInstanceID(), objPool);
return objPool;
}
public static void PushObjectPool(Transform handleTransform)
{
ObjectPool objPool = GetPoolByTransform(handleTransform);
if (objPool)
{
objPool.PushObjectToPool(handleTransform);
}
else
{
GameObject.Destroy(handleTransform.gameObject);
}
}
public static void PushObjectPool(Transform handleTransform, float delayTime)
{
ObjectPool objPool = GetPoolByTransform(handleTransform);
if (objPool)
{
objPool.PushObjectToPool(handleTransform, delayTime);
}
else
{
GameObject.Destroy(handleTransform.gameObject, delayTime);
}
}
//马上回池的接口
public static void PushObjectPool(Transform handleTransform, GameObject prefab)
{
ObjectPool objPool = GetObjectPool(prefab);
objPool.PushObjectToPool(handleTransform.transform);
}
//延迟回池的接口
public static void PushObjectPool(Transform handleTransform, GameObject prefab, float delayTime)
{
ObjectPool objPool = GetObjectPool(prefab);
objPool.PushObjectToPool(handleTransform, delayTime);
}
private static ObjectPool GetPoolByTransform(Transform handleTransform)
{
if (transformDic.ContainsKey(handleTransform))
{
return transformDic[handleTransform];
}
Debug.LogError(handleTransform.name + " no find it's ObjectPool");
return null;
}
// add code to clean pool from time to time.
private static void StartThreadOnce()
{
// start thread to clean pool from time to time.
if (!bStartThreadOnce)
{
bStartThreadOnce = true;
ThreadPool.QueueUserWorkItem(AutoToCheckOptimization);
}
}
private static void AutoToCheckOptimization(object obj)
{
Thread.Sleep(10);
isStartThread = true;
tOptimizationThread = new Thread(OptimizationPool);
tOptimizationThread.Start();
}
private static void OptimizationPool()
{
// check cpu time to start
Thread.Sleep(100);
// 检測间隔时间20秒
float intervalTimeTodetect = 20f;
// after how many times counts to reset count.
// 循环检測多少次,后记录清零。每次仅仅处理须要处理的前5个池。
const int checkTimesForEach = 5;
// 暂时池管理对象
Dictionary<int, ObjectPool> poolManagerTempDic = new Dictionary<int, ObjectPool>();
System.DateTime timeCount = System.DateTime.Now;
// 间隔时间内运行一次
bool eachMinuteGetDicOnce = false;
// 每一个池未使用对象超过一半的标记,记录次数
Dictionary<int, int> CurrentPoolUnuseCount = new Dictionary<int, int>();
// 检測刷新次数。也是一次计数的最大时间。
int icountLoopTime = 0;
Debug.Log("Thread start");
// 休眠时间
int sleepTime = 10;
while (isStartThread)
{
Thread.Sleep(sleepTime);
if (!eachMinuteGetDicOnce)
{
eachMinuteGetDicOnce = true;
poolManagerTempDic = poolManagerDic.ToDictionary(entry => entry.Key,entry => entry.Value);
// loop check 3 time to reset.
if (icountLoopTime % checkTimesForEach == 0)
{
CurrentPoolUnuseCount.Clear();
icountLoopTime = icountLoopTime > 10000000 ? 0 : icountLoopTime;
}
// mark unuse nuber for all.
foreach(var element in poolManagerTempDic)
{
ObjectPool opool = element.Value;
int unusinglinkCount = opool.UnusingLinkedList.Count;
// half of all is useless and more than 10.
if (unusinglinkCount * 2 > unusinglinkCount + opool.UsingLinkedList.Count && unusinglinkCount > 10)
{
MarkCountForUnusingLink(ref CurrentPoolUnuseCount, element.Key);
// satisfy the condition,add unusing link gameobject to garbagelist.
int currentMark = 0;
CurrentPoolUnuseCount.TryGetValue(element.Key,out currentMark);
// be marked three times, add to garbage list.
if (currentMark >= 3)
{
AddObjectsToGarbageList(ref opool.UnusingLinkedList);
// count tick to reset.
CurrentPoolUnuseCount[element.Key] = 0;
clearGarbageFlag = true;
// each time only gathing one pool to process.
break;
}
}
}
}
// leave time for mainthread to delete gameobjects.
if (clearGarbageFlag)
{
icountLoopTime = 0;
intervalTimeTodetect = 20f;
Thread.Sleep(1000);
timeCount = System.DateTime.Now;
}
// interval 20 seconds to start check once;
if ((System.DateTime.Now - timeCount).TotalSeconds > intervalTimeTodetect)
{
timeCount = System.DateTime.Now;
eachMinuteGetDicOnce = false;
poolManagerTempDic.Clear();
icountLoopTime++;
Debug.Log("Loop count is " + icountLoopTime);
}
// long time nothing happen, expand the detective interval time (max <= 90s).
if (icountLoopTime >= 4 )
{
intervalTimeTodetect = intervalTimeTodetect * 2 >= 90f ? 90f : intervalTimeTodetect * 2;
icountLoopTime = 0;
Debug.Log("interval time is " + intervalTimeTodetect);
}
}
return;
}
private static void MarkCountForUnusingLink(ref Dictionary<int, int> poolUnuseCount,int prefabGuid)
{
Debug.Assert(null != poolManagerDic);
int currentMark = 0;
if (poolUnuseCount.ContainsKey(prefabGuid))
{
poolUnuseCount.TryGetValue(prefabGuid, out currentMark);
}
currentMark++;
if (poolUnuseCount.ContainsKey(prefabGuid))
{
poolUnuseCount[prefabGuid] = currentMark;
}
else
{
poolUnuseCount.Add(prefabGuid, currentMark);
}
}
/// <summary>
/// add last gameobject to garbagelist,as when unsing unusinglink is from first place to get.
/// </summary>
/// <param name="list"></param>
private static void AddObjectsToGarbageList(ref LinkedList<Transform> list)
{
Debug.Assert(list.Count > 0);
int FlagDestroyNumber = list.Count>>1;
for (int i = 0; i < FlagDestroyNumber; i++)
{
GarbageList.Add(list.Last.Value);
list.RemoveLast();
}
}
public void Dispose()
{
isStartThread = false;
}
}
}
simple_pool对象池——优化<二>的更多相关文章
- Unity 游戏框架搭建 (二十) 更安全的对象池
上篇文章介绍了,只需通过实现IObjectFactory接口和继承Pool类,就可以很方便地实现一个SimpleObjectPool.SimpleObjectPool可以满足大部分的对象池的需求.而笔 ...
- 二十、dbms_stats(用于搜集,查看,修改数据库对象的优化统计信息)
1.概述 作用:用于搜集,查看,修改数据库对象的优化统计信息. 2.包的组成 1).get_column_stats作用:用于取得列的统计信息语法:dbms_stats.get_column_stat ...
- Unity性能优化-对象池
1.对象池Object Pool的原理: 有些GameObject是在游戏中需要频繁生成并销毁的(比如射击游戏中的子弹),以前的常规做法是:Instantiate不断生成预设件Prefab,然后采用碰 ...
- Mysql线程池优化笔记
Mysql线程池优化我是总结了一个站长的3篇文章了,这里我整理到一起来本文章就分为三个优化段了,下面一起来看看. Mysql线程池系列一(Thread pool FAQ) 首先介绍什么是mys ...
- Unity 对象池的使用
在游戏开发过程中,我们经常会遇到游戏发布后,测试时玩着玩着明显的感觉到有卡顿现象.出现这种现象的有两个原因:一是游戏优化的不够好或者游戏逻辑本身设计的就有问题,二是手机硬件不行.好吧,对于作为程序员的 ...
- Unity 游戏框架搭建 (十九) 简易对象池
在Unity中我们经常会用到对象池,使用对象池无非就是解决两个问题: 一是减少new时候寻址造成的消耗,该消耗的原因是内存碎片. 二是减少Object.Instantiate时内部进行序列化和反序列化 ...
- Java小对象的解决之道——对象池(Object Pool)的设计与应用
一.概述 面向对象编程是软件开发中的一项利器,现已经成为大多数编程人员的编程思路.很多高级计算机语言也对这种编程模式提供了很好的支持,例如C++.Object Pascal.Java等.曾经有大量的软 ...
- 大数据技术之_27_电商平台数据分析项目_02_预备知识 + Scala + Spark Core + Spark SQL + Spark Streaming + Java 对象池
第0章 预备知识0.1 Scala0.1.1 Scala 操作符0.1.2 拉链操作0.2 Spark Core0.2.1 Spark RDD 持久化0.2.2 Spark 共享变量0.3 Spark ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...
随机推荐
- JD2
Business Requirement Support l Develops and communicates plan to manage vendor review of requirement ...
- Linux查看哪些进程用了Swap分区
如果系统的物理内存用光了,则会用到swap.系统就会跑得很慢,但仍能运行;如果Swap空间用光了,那么系统就会发生错误.通常会出现“application is out of memory”的错误,严 ...
- 【MySQL】undo,redo,2PC,恢复思维导图
http://blog.itpub.net/22664653/viewspace-2131353/
- 去掉wget烦人的 “eta(英国中部时间)” 提示
gentoo 里的 wget ,从1.12版本开始,就一直有个不影响功能的小毛病:由于中文翻译的失误,进度提示的时候,会被拉成很多行.原因就是原来英文的ETA这3个字母,被翻译成了 “eta(英国中部 ...
- Linux命令基本格式
1 起始符td@td-Lenovo-IdeaPad-Y410P:~$ 第一个td表示当前登录管理员名,中间@无实际意义,td-Lenovo-IdeaPad-Y410P表示主机名,-表示当前所在目录(h ...
- ylb: 数据库操作方法基础
ylbtech-SQL Server:SQL Server-数据库操作方法基础 数据库操作方法基础. ylb: 数据库操作方法基础 返回顶部 ----------试图操作(view)--------- ...
- Makefile之“=”、":="、“+=”、“?=”
Makefile之“=”.":=".“+=”.“?=”中几个的区别: 1.”=“符号 =表示个变量赋值: 注意: 当变量A被赋值给变量B时(B=A),这里A可以的这条指令之前定义的 ...
- Tomcat:Java Web服务器配置详解
一.Tomcat概述 1.tomcat简介 tomcat是基于JDK的web服务器,其能运行Servlet和JSP规范总.Tomcat 5支持最新的Servlet 2.4 和JSP 2.0规范.实际上 ...
- c#中的构造方法
c#基础--类的构造方法 当实例化一个类时,系统会自动对这个类的属性进行初始化 数字型初始化成0/0.0 string类型初始化成null char类型初始化成\0 构造器就是构造方法,能够被重载 ...
- 前端存储之Web Sql Database
前言 在上一篇前端存储之indexedDB中说到,我们项目组要搞一个前后端分离的项目,要求在前端实现存储,我们首先找到了indexedDB,而我们研究了一段时间的indexedDB后,发现它并不是很适 ...