Unity List底层源码剖析

本文详细分析了C#List类的源码实现,涉及Add、Remove、Insert等接口,以及内存管理策略。强调了线程安全问题,并指出在项目优化中要合理利用List的灵活性和高效性,同时注意内存效率和线程同步。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印


前言

没有扎实的基础,很多编写的程序会随着软件规模的扩大或扩展而产生诸多问题,然后这些程序很可能会被无情的抛弃并重写。而其中的问题可能只是因为一点点的小问题堆积起来,基础可见其重要。本章我们将深入了解经常使用的List。

我曾经在学校学习过链表、列表等数据结构,但实际上当时并没有真正理解,只是简单地复制粘贴代码。我觉得自己的基础很差。后来在工作中遇到一些基础问题或者想要了解某些内部原理时,总是依赖查找资料。如果你也想深入了解C#,我推荐购买《C#图解教程》当作查阅资料。


一、List源码

List是C#中一个最常见的可伸缩数组组件,通常我们在编写程序时代替数组,因为其不用分配数组大小,很是方便。

首先,我们看下内部构造,源码如下:

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];

    // 构建一个列表,该列表最初是空的,容量为零
    // 将第一个元素添加到列表后,容量将增加到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继承IList、IReadOnlyList两个接口,list内部其实还是数组实现的,不是链表,初始容量为0。那我们不经思考,我们进行添加操作和删除时内部如何运行?

List源码网址为:官方跳转链接
IList源码网址为:官方跳转链接
IReadOnlyList源码网址为:官方跳转链接

二、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;
    }
}

在添加数据的时候首先会检测数组的容量够不够,够就将新的数据进行赋值,不够则调用EnsureCapacity方法增加容量。而在容量不够的时候会进行扩容操作。

 int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;

也就是扩充一倍,4变8,8变16,愈演愈烈。那么其优缺点就显而易见了,优点是使用索引的方式提取元素十分方便,缺点是扩容导致的new操作造成内存垃圾,给GC带来很大负担。源码中按照2的指数扩容的方式是为了降低GC负担,如果连续申请扩容,会浪费大量的内存空间;如果数据量大的时候1024直接扩容到2048也会造成大量的内存空间的浪费。怎么解决呢,我们先研究下其他的接口再来做决定。

三、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

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值