Unity中List的底层源码剖析

1、List底层代码剖析

List是C#中一个最常见的可伸缩数组组件,我们常用它来代替数组。因为它是可伸缩的,所以我们在编写程序的时候不用手动去分配大小,接下来我们来看看list的底层实现。

public class list<T> :IList <T>,System.Collections.IList, IReadOnlyList <T>
{
    private const int _ defaultCapacity = 4;
    private T[] _items;
    private int _size;
    private int _version;
    private Object _syncRoot;
    static readonly T[] _emptyArray = new T[0];

    //构建一个列表,该列表最初是空的,容量为0
    //将第一个元素添加到列表后,容量将增加到16,然后根据需要以2的倍数增加
    public List()
    {
        _items = _emptyArray;
    }
    
    //构造具有给定容量的List。该列表最初为空的。但是在需要重新分配之前,会为给定数量的元素流出空间
    public List(int capacity)
    {
        if(capacity < 0)             
            ThrowHelper.ThrowArgumentOutOfRangeException(
            ExceptionArgument.capacity,             
            ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
        Contract.EndContractBlock();
        
        if(capacity == 0)
            _items = _emptyArray;
        else
            _items = new T[capacity]; 
    }

    //******其他内容
}

构造函数部分可以知道List内部使用数组实现的,而不是链表,并且当没有给予指定容量时,初始的容量为0。也就是说,List组件在被Add()、Remove()两个函数调用时,都是采用“在数组上对元素进行转移的操作,或者从原数组复制生成到新数组”的方式工作的

2.Add接口剖析

Add接口源码如下

//将给定对象添加到此列表的末尾。列表的大小增加1
//如果需要,再添加新元素之前,列表的容量会增加1
public void Add(T item)
{
    if(_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size++] = item;
    _version++;
}

//如果列表的当前容量小于min,则容量将增加到当前容量的两倍或min,以较大者为准
private void EnsureCapacity(int min)
{
    if(_items.Length < min){
        int newCapacity = _items.Length == 0?_defaultCapacity : _items.Length * 2;
    
        //在遇到溢出前,允许列表增加到最大可能的容量(约2GB元素)
        //请注意,即使_items.Length由于(uint)强制转化而溢出,次检查任然有效
        if((uint)newCapacity > Array.MaxArrayLength) 
            newCapacity = Array.MaxArrayLength;
        if(newCapacity < min) 
            newCapacity = min;
        Capacity = newCapacity
    }
}

上述List源码中的Add函数,每次增加一个元素的数据,Add接口都会首先检查容量够不够,如果不够就调用EnsureCapacity函数来增加容量。每次容量不够的时候,整个数组的容量都会扩大一倍,_defaultCapacity表示容量默认大小为4,因此扩充路线为4、8、16、32、64、128、256、512、1024...........以此类推。

List使用数组形式作为底层数据结构,优点是使用索引方式获取元素很快。缺点是扩容时会很糟糕,每次针对数组进行new操作都会造成内存垃圾,这会给垃圾回收(GC)带来很大负担。

这里以2的倍数扩容的方式可以为GC减轻负担,但是如果数组被连续申请扩容,还是会造成GC的不小负担,特别是代码中的List频繁使用Add时。此外如果数量使用不当,会浪费大量内存空间,例如当元素的数量为520时,List会被扩容到1024个元素,如果不使用剩余的504个空间单位,就会造成大部分内存空间的浪费。

3.Remove接口剖析

下面为Remove接口的源码

//删除给定索引处的元素。列表的大小减1
public bool Remove(T item)
{
    int index = IndexOf(item);
    if(index >= 0)
    {
        RemoveAt(index);
        return true;
    }

    return false;
}

//返回此列表范围内给定值首次出现的索引
//该列表从头到尾向前搜索
//使用Object.Equals方法将列表中的元素与给定值进行比较
//此方法使用Array.IndexOf方法进行搜索
public int IndexOf(T item)
{
    Contract.Ensures(Contract.Result<int>() >= -1)
    Contract.Ensures(Contract.Result<int>() < Count)
    return Array.IndexOf(_items, item, 0, _size);
}

//删除给定索引处的元素,列表的大小减1
public void RemoveAt(int index)
{
    if((uint)index >= (uint)_size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    Contract.EndContractBlock();
    _size--;
    if(index < _size)
    {
        Array.Copy(_items, index + 1, _items, index, _size - index);
    }
    _items[_size] = default(T);
    _version++;
}

Remove函数中包含IndexOf和RemoveAt函数,其中使用IndexOf函数是为了找到元素的索引位置,使用RemoveAt可以删除指定位置的元素。

从源码中可以看到,元素删除的原理就是使用Array.Copy对数组进行覆盖。IndexOf是用Array.IndexOf接口来查找元素的索引位置,这个接口本身的内部实现就是按索引顺序从0到n对每个位置进行比较。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity商业游戏底层资源加载框架的相关源码可以通过Unity官方提供的AssetBundle类来实现。AssetBundle类是Unity用于打包和加载资源的类,它可以将游戏的各种资源(例如场景、模型、纹理、音频等)打包为一个个独立的AssetBundle文件,然后在运行时动态加载这些文件。 以下是实现资源加载的简单示例代码: ```csharp using UnityEngine; public class ResourceManager : MonoBehaviour { // 资源加载路径 public string bundleURL; // 资源名称 public string assetName; // 加载完成后的资源对象 private GameObject loadedAsset; void Start() { // 启动异步加载资源的协程 StartCoroutine(LoadAssetBundle()); } // 异步加载资源的协程 IEnumerator LoadAssetBundle() { // 使用UnityWebRequest从指定URL加载AssetBundle文件 using (var request = UnityEngine.Networking.UnityWebRequestAssetBundle.GetAssetBundle(bundleURL)) { yield return request.SendWebRequest(); // 加载AssetBundle文件 var bundle = DownloadHandlerAssetBundle.GetContent(request); // 从AssetBundle异步加载资源 var assetRequest = bundle.LoadAssetAsync<GameObject>(assetName); yield return assetRequest; // 获取加载完成的资源对象 loadedAsset = assetRequest.asset as GameObject; } // 使用加载完成的资源对象进行后续操作 if (loadedAsset != null) { Instantiate(loadedAsset, transform.position, Quaternion.identity); } } } ``` 以上代码在Start方法启动了一个协程,通过UnityWebRequest异步地从指定URL加载AssetBundle文件,然后使用AssetBundle.LoadAssetAsync异步地加载指定名称的资源,最后获取加载完成的资源对象进行后续操作。 注意:上述代码仅为示例,具体的资源加载逻辑和流程可能因项目需求而异,需要根据实际情况进行相应的修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值