C#容器——数组和列表

2020-12-05  本文已影响0人  太刀

C# 中数组和列表通常用来存放一系列元素

1. 数组

1.1 数组的常见用法

        // 长度固定,在声明时指定
        int[] intArr = new int[5];

        // 使用下标读写
        intArr[0] = 100;
        int value = intArr[1];
        // 遍历数组
        for (int i = 0; i < intArr.Length; i++)
        {
            Debug.Log(string.Format("{0}:{1}", i, intArr[i]));
        }

        int index = 0;
        // 使用迭代器遍历
        IEnumerator enumerator = intArr.GetEnumerator();
        while (enumerator.MoveNext())
        {
            Debug.Log(string.Format("{0}:{1}", index++, enumerator.Current));
        }

        index = 0;
        // 另外一种遍历
        foreach (int v in intArr)
        {
            Debug.Log(string.Format("{0}:{1}", index ++, v));
        } 
        int[] anotherArr = intArr.Clone() as int[]; 

1.2 抽象类 Array

Array源码

        // 排序
        Array.Sort(intArr);

        // 查找下标
        int indexOf2 = Array.IndexOf<int>(intArr, 2);

        // 反转数组
        Array.Reverse(intArr);

        // 二分查找
        int indexOf3 = Array.BinarySearch<int>(intArr, 3);

        // 复制
        Array.Copy(intArr, anotherArr, intArr.Length);

1.3 数组优缺点

Array(数组)的特点

优点:

缺点:

2. ArrayList

ArrayList源码
ArrayList 是一个非泛型类,实现了 IListIEnumerable 接口

2.1 ArrayList的常见用法

        // 添加元素
        al.Add(1);
        al.Add("one");

        // 下标访问
        al[1] = 100;
        string value = al[2] as string;
        
        // 遍历,同样可以用 foreach 和迭代器来遍历
        for (int i = 0; i < al.Count; i ++)
        {
            Debug.Log(string.Format("{0}:{1}", i, al[i]));
        }        

        // 删除元素
        al.Remove("one");
        al.RemoveAt(0);

        // 是否包含元素
        bool contains = al.Contains("one");

        // 清除列表
        al.Clear();

,用来存放数据列表,元素可以是不同类型(存储时都使用了 object 类型)

2.2 ArrayList 的底层存储结构

查看源码可以看到,ArrayList的存储字段是

public Object[] _items;

可见,它的底层存储结构是 object类型数组,当添加或插入值类型对象时,会进行一次装箱,类似的,读取时会进行一次拆箱

2.3 ArrayList的扩容

2.4 ArrayList 添加元素

AddAddRange方法在尾部添加元素

2.5 ArrayList 插入元素

InsertInsertRange 方法在指定位置插入元素
无论是否触发扩容,都需要搬运元素(无扩容时,所有插入位置后的元素要搬运到对应的下标处),时间复杂度 O(n)

2.6 ArrayList 查找元素

Contains方法返回列表中是否存在元素,可定制 comparerIndexOf 方法找到元素下标,BinarySearch方法返回元素下标,查找方法的时间复杂度都是 O(lg n)

3.3 ArrayList 移除元素

包括RemoveRemoveAt方法
Remove 参数为元素对象时,先调用方法 IndexOf 找到下标,再调用 RemoveAt 方法进行移除,该方法会导致数组元素的位置变化,所以移除时间复杂度O(n)

3.4 ArrayList 优缺点

优点:

缺点:

4. List

List源码
List 是一个泛型类,在初始化时必须指定元素的类型

4. 1 List常见用法

        // 声明
        List<int> l = new List<int>();

        // 添加元素
        l.Add(1);
        l.AddRange(new int[] { 1, 2, 3 });

        // 下标访问
        l[2] = 5;
        int value = l[3];

        // 插入元素
        l.Insert(1, 56);

        // 查找
        int index = l.IndexOf(56);
        bool contains = l.Contains(56);

        // 遍历
        List<int>.Enumerator enumerator = l.GetEnumerator();
        while (enumerator.MoveNext())
        {
            Debug.Log("value:" + enumerator.Current);
        }

        // 移除
        l.RemoveAt(0);
        l.Remove(1);
4.1 List 的存储结构
public T[] _items;
4.2 元素添加和移除

执行逻辑和 ArrayList 一样,方法为 Add, AddRange, Remove, RemoveAt, RemoveAll 等,时间复杂度也一样

4.3 List 的元素查找

ArrayList 一样,ContainsIndexOfBinarySearch方法都是二分查找,时间复杂度 O(lg n)

4.4 List 的优缺点

的优点:

缺点:
扩容导致的性能和空间损耗
插入元素导致的元素移位,复杂度O(n)

5. LinkedList

LinkedList源码
LinkedList不再实现 IList接口,但实现了 ICollection 接口,LinkedList 是一个泛型类,使用时需要指定元素类型

5.1 LinkedList 的常见用法

        // 声明
        LinkedList<int> ll = new LinkedList<int>();

        // 在头尾添加元素
        ll.AddFirst(1);
        ll.AddLast(100);

        // 在对应节点前后添加元素
        ll.AddBefore(ll.Find(1), 2);
        ll.AddAfter(ll.Find(100), 101);

        // 遍历
        LinkedList<int>.Enumerator enumerator = ll.GetEnumerator();        
        while (enumerator.MoveNext())
        {
            Debug.Log(enumerator.Current);
        }

        // 另外一种遍历方法
        LinkedListNode<int> cur = ll.First;
        while (cur.Next != null)
        {
            Debug.Log(cur.Value);
            cur = cur.Next;
        }

        // 长度
        int count = ll.Count;

        // 移除
        ll.Remove(100);
        ll.RemoveFirst();
        ll.RemoveLast();

        // 查找
        bool contains = ll.Contains(100);
        LinkedListNode<int> node = ll.Find(100);  

5.2 LinkedList 的存储结构

LinkedList的存储结构是双向链表,关联结构体 LinkedListNode:

internal LinkedListNode<T> head;

使用双向链表来存储数据,保留了表头的引用,需要配合链表类型 LinkedListNode 来使用

5.3 LinkedList 的添加和插入

AddAfterAddBefore 方法,在某节点的前面或后面插入节点,时间复杂度 O(1)
AddFirstAddLast 方法,在头部或尾部插入节点,时间复杂度 O(1)

5.4 LinkedList 的删除

RemoveRemoveFirstRemoveLast 都是重置节点的 next 和 last 指针,时间复杂度 O(1)

5.5 LinkedList 的查找

ContainsFind 方法,都是从头指针开始遍历,时间复杂度 O(n)

5.6 LinkedList 的优缺点

优点:

缺点:

6. 总结
容器类型 存储方式 类型固定 下标访问 额外空间 添加和插入复杂度 删除复杂度 查找复杂度
数组Array 连续内存存储 类型固定 支持 固定长度,无额外空间 x x 二分查找 O(lgn)
ArrayList 数组 类型灵活 支持 2倍扩容,可能产生额外空间 O(n) O(n) 二分查找 O(lgn)
List<T> 数组 类型固定 支持 2倍扩容,可能产生额外空间 O(n) O(n) 二分查找 O(lgn)
LinkedList<T> 双向链表 类型固定 不支持 每个元素额外两个指针;无需扩容 O(1) O(1) 从头指针遍历O(n)

参考:
C#容器源码

上一篇 下一篇

猜你喜欢

热点阅读