目录
基本概念
List类是ArrayList类的泛型版本,相较于自由的ArrayList,List需要在定义时提前指定其内部元素的数据类型,但相对的,List相较于ArrayList在使用其内部元素时也不需要进行装箱、拆箱等操作,在存储元素为值类型的情况下List会有更为优秀的性能表现。
与ArrayList相同,当使用List.Add()向List当中置入数据时,若List空间不足,则List会自动扩容,因此使用List不必担心像数组一样出现空间不足的情况。
但与数组相似的是,当使用索引对List元素进行访问时,即:List[index],可能会像数组一样出现越界问题,而List的范围则是[0,List.Count),即访问索引必须大于等于0并小于List.Count。
基本属性
Count
上文说到,List的范围是[0,Count),即访问索引必须大于等于0并小于Count,因此Count也可视作List内部元素的数量
Capacity
List.Capacity是List的实际容量,这个容量对于同一个List只会扩张不会缩小,当使用了List.Remove()等操作时只会缩小Count,对Capacity不会产生影响;这意味着对于同一个List对象,即便是移除了内部元素,List的实际占用空间也不会缩小,但如果List内部元素是引用类型,则会删除其引用。
同时,如果引发了List的扩容,则Capacity会扩张到原来的两倍,但并不是直接扩张原来的空间大小,而是申请一片新的内存空间,并将原有元素拷贝到新空间当中,因此扩容因造成一定的性能消耗。
构造List
无参构造
这是最基本的List构造方法,但大多数情况下推荐使用其他方法优化性能
List<int> _list_None = new List<int>();
一参构造(int)
这个构造函数事实上是用于指定List生成后的容量,即List.Capacity,在已知元素大致数量的情况下,使用该构造函数可以减少List的扩容,从而减少性能损耗。
List<int> _list_Capaacity = new List<int>(8);
一参构造 (Array)
List也可以使用元素类型相同的一维数组进行构造,用这种方法生成的List其Count与Capacity都取决于传入的数组大小
int[] _array = new[] { 0, 1, 2 };
List<int> _list_Array = new List<int>(_array);
拷贝构造
List也可以使用其他List对象进行构造,使用这种方法生成的List其Count与Capacity都取决于拷贝的List对象的Count
List<int> _list_List = new List<int>(8);
//生成后实际Count和Capacity都为0,因为其拷贝的List是空的
List<int> _list_OtherList = new List<int>(_list_List);
其他
List其实也可以使用其他集合进行构造,比如:
Dictionary<int, int> _dictionary = new Dictionary<int, int>(4);
List<int> _lsit_Key = new List<int>(_dictionary.Keys);
List<int> _lsit_Value = new List<int>(_dictionary.Values);
又或者:
List<int> _list_Element = new List<int>()
{
0, 1, 2, 3, 4, 5
};
常用API
Add(T)
List.Add()用于向List中添加元素,新添加的元素会存在于List尾部,当List容量不足时会引起扩容。
List<int> _list = new List<int>(2);
_list.Add(0);// Capacity:2 ,内部元素:0
_list.Add(1);// Capacity:2 ,内部元素:0, 1
//扩容
_list.Add(2);// Capacity:4 ,内部元素:0, 1, 2
_list.Add(3);// Capacity:4 ,内部元素:0, 1, 2, 3
AddRange(Predicate<T>)
List.AddRange()用于将其他集合中的内容拷贝到当前List的尾部
List<int> _list = new List<int>(6)
{
0, 1, 2, 3, 2, 3
};
List<int> _list_AddRange = new List<int>()
{
9, 8, 7
};
_list_AddRange.AddRange(_list);// 内部元素为:9, 8, 7, 0, 1, 2, 3, 2, 3
Remove()
List.Remove()用于移除指定元素,但只会移除第一个匹配项,未找到匹配项时,该函数不会有额外操作。
List<int> _list = new List<int>(6)
{
0, 1, 2, 3, 2, 3
};
_list.Remove(4);//元素:0, 1, 2, 3, 2, 3,未找到则不生效
_list.Remove(2);//元素:0, 1, 3, 2, 3
Clear()
List.Clear()用于清除List当中的全部元素
List<int> _list = new List<int>(6)
{
0, 1, 2, 3, 2, 3
};
_list.Clear();//什么都没了,Count == 0
其他API
- BinarySearch(T):搜索整个已排序的 List<T> 中的元素,并返回指定对象的从零开始的索引。
- Contains(T):确定某元素是否在List<T>中。
- ConvertAll<TOutput>(Converter<T,TOutput>):将 List<T> 中的所有元素转换为指定类型,并返回包含转换后元素的数组。
- CopyTo(T[], Int32):从目标数组的指定索引处开始,将整个 List<T> 复制到兼容的一维数组中。
- Exists(Predicate<T>):确定 List<T> 中是否存在与指定谓词定义的条件匹配的元素。
- Find(Predicate<T>):检索与指定谓词定义的条件匹配的第一个元素。
- FindAll(Predicate<T>):检索与指定谓词定义的条件匹配的所有元素。
- FindIndex(Predicate<T>):检索与指定谓词定义的条件匹配的第一个元素的从零开始的索引。
- FindLast(Predicate<T>):检索与指定谓词定义的条件匹配的最后一个元素。
- FindLastIndex(Predicate<T>):检索与指定谓词定义的条件匹配的最后一个元素的从零开始的索引。
- ForEach(Action<T>):对 List<T> 中的每个元素执行指定操作。
- GetEnumerator():返回循环访问 List<T> 的枚举器。
- GetRange(Int32, Int32):返回一个新 List<T>,它包含源 List<T> 中某个范围内的元素。
- IndexOf(T):搜索指定对象,并返回整个 List<T> 中第一个匹配项从零开始的索引。
- Insert(Int32, T):在 List<T> 的指定索引处插入一个元素。
- InsertRange(Int32, IEnumerable<T>):在 List<T> 的指定索引处插入集合中所有元素。
- LastIndexOf(T):搜索指定对象,并返回整个 List<T> 中最后一个匹配项从零开始的索引。
- RemoveAll(Predicate<T>):从 List<T> 中删除与指定谓词定义的条件匹配的所有元素。
- RemoveAt(Int32):删除 List<T> 的指定索引处的元素。
- RemoveRange(Int32, Int32):从 List<T> 中移除某个范围内的所有元素。
- Reverse():反转整个 List<T> 中所有元素顺序。
- Sort():使用默认比较器对整个 List<T> 中所有元素进行排序。
特别提醒
- 如果List内部的元素是值类型的复合数据类型,那不要试图通过索引直接修改该复合数据类型的内部成员,因为List返回的对象事实上是该数据的拷贝,但如果是引用类型,则返回的是该引用类型则返回的是引用:
class MainClass
{
struct MyStruct
{
public int data;
public MyStruct(int data)
{
this.data = data;
}
}
static void Main(string[] args)
{
List<MyStruct> _list_Struct = new List<MyStruct>() { new MyStruct(5) };
_list_Struct[0].data = 10;//报错
}
}
- 因为List是引用类型,所以如果使用了"="将一个List对象赋值给另一个List对象,那对这两个对象进行操作本质上都是操作同一份数据
List<int> _listA = new List<int>() { 0, 1, 2, 3 }; List<int> _listB = _listA; _listB[0] = 3;//此时会发现 _listA[0] 也变成了3