C# 常用数据结构之列表List<T>
List<T> 是 C# 中使用非常频繁的一种数据结构,我习惯称之为“列表”!
前面整理了一下数组的用法,我们可以发现数组有一个致命的缺陷,那就是固定长度,这就导致了数组的使用范围比较有限。
List<T> 和 ArrayList 就解决了这个问题,这两种数据结构本质上都是数组,但他们是“动态数组”,长度可变!
不过 ArrayList 是属于被抛弃的那种,一般都不怎么被使用的,Why?
因为 ArrayList 有一个骚操作,就是会把所有元素都当做 Object 处理,这就导致在对值类型的元素进行操作的时候会进行装箱和拆箱,如果装箱和拆箱比较频繁,性能有点刚不住!
而 List<T> 作为 ArrayList 的泛型等效类,加上了类型 T 的限制,不仅解决了装箱拆箱导致的性能问题,同时也保证了类型安全!
so,可以忽略掉 ArrayList 这个数据结构了,因为 ArrayList 有的 List<T> 全都有,还补足了各种缺陷!
好了,回归正题,看看 List<T> 到底该怎么用~
List<T> 的命名空间:System.Collections.Generic
特性
1、顺序存储,改查快,增(插入)删慢;
- 使用 Add 将新元素添加在末尾是很快的,但是使用 Insert 将新元素添加在其他位置就不同了。因为这样会导致后面元素的移动位置(后移),删除同样道理,会导致后面元素的位置前移。这样效率是很低的!
2、长度可变,容量不够会自动扩容;
- 每次扩容,容量增加一倍。
3、需要指定数据类型,保证类型安全;
- 同时也避免了 ArrayList 可能出现的装箱拆箱操作。
初始化
List<T> 的初始化有以下三种方式:
1、List<T>() 初始化 List<T> 类的新实例,该实例为空并且具有默认初始容量。
List<int> intList = new List<int>();
Console.WriteLine("intList 的默认容量为:{0}", intList.Capacity);
intList.Add(1);
intList.Add(2);
intList.Add(3);
foreach(var value in intList)
{
Console.Write(value + " ");
}
Console.WriteLine();
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity);
/*
intList 的默认容量为:0
1 2 3
intList 的当前容量为:4
*/
ps
- 当向 List<T> 中添加元素时,如果容量不够会自动扩容,扩容会新建一个数组,将原数组里面的数据拷贝进去;
- 当从 List<T> 中删除元素后,可以通过调用 TrimExcess 方法或通过显式设置属性来减少容量 Capacity,减少容量会重新分配内存,并拷贝 List<T> 中的所有元素。
2、List<T>(IEnumerable<T>) 初始化 List<T> 类的新实例,该实例包含从指定集合复制的元素并且具有足够的容量来容纳所复制的元素。
int[] intArray = {1, 2, 3};
List<int> intList = new List<int>(intArray);
foreach(var value in intList)
{
Console.Write(value + " ");
}
// 1 2 3
也可以使用这种简化的写法:
List<int> intList = new List<int> {1, 2, 3};
foreach(var value in intList)
{
Console.Write(value + " ");
}
// 1 2 3
3、List<T>(Int32) 初始化 List<T> 类的新实例,该实例为空并且具有指定的初始容量。
List<int> intList = new List<int>(16);
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:16
ps
如果集合的大小为可估算值,建议使用这种初始化方式给 List<T> 指定一个合适的容量,避免在插入元素的时候动态扩容造成的性能消耗!
常用属性
Capacity
获取或设置该内部数据结构在不调整大小的情况下能够容纳的元素总数。
List<int> intList = new List<int> {1, 2, 3};
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:4
intList.Capacity = 3;
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:3
intList.Add(4);
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:6
容量始终大于或等于元素的数量。如果在添加元素的时候元素的数量超过了容量,则会在复制旧元素并添加新元素之前自动重新分配内部数组,从而增加容量(自动扩容)。
Count
获取 List<T> 中包含的元素数量。
List<int> intList = new List<int> {1, 2, 3};
Console.WriteLine("intList 中的元素数量为:{0}", intList.Count); // intList 中的元素数量为:3
常用方法
Add
将对象添加到 List<T> 的结尾处。
List<string> strList = new List<string>();
strList.Add("a");
strList.Add("b");
strList.Add("c");
foreach(var element in strList)
{
Console.Write(element + " ");
}
// a b c
Clear
从 List<T> 中移除所有元素。
List<bool> boolList = new List<bool> {false, true, false};
Console.WriteLine("intList 中的元素数量为:{0}", boolList.Count); // intList 中的元素数量为:3
boolList.Clear();
Console.WriteLine("intList 中的元素数量为:{0}", boolList.Count); // intList 中的元素数量为:0
Console.WriteLine("intList 的当前容量为:{0}", boolList.Capacity); // intList 的当前容量为:4
ps
清空或删除元素不会改变 List<T> 的容量,只会主动扩容,不会主动缩容!
Contains
确定某元素是否在 List<T> 中。
List<int> strList = new List<int> {1, 666, 3};
Console.WriteLine(strList.Contains(666) ? "元素存在" : "元素不存在"); // 元素存在
Find
搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List<T> 中的第一个匹配元素。
List<int> intList = new List<int> {1, 2, 3};
int ret = intList.Find(x => x > 2);
Console.WriteLine(ret); // 3
ps
Find系还有几个方法:FindAll、FindIndex、FindLast、FindLastIndex,这里就不一一赘述了!
IndexOf
返回 List<T> 或它的一部分中某个值的第一个匹配项的从零开始的索引。
List<string> strList = new List<string> {"a", "b", "c"};
Console.WriteLine(strList[1]); // b
Console.WriteLine(strList.IndexOf("b")); // 1
Insert
将元素插入 List<T> 的指定索引处。
List<int> intList = new List<int> {1, 2, 3};
intList.Insert(1, 666);
intList.Insert(2, 888);
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 1 666 888 2 3
ps
插入操作会导致从插入下标位置开始往后的元素后移。
Remove
从 List<T> 中移除特定对象的第一个匹配项。
List<int> intList = new List<int> {1, 1, 2};
intList.Remove(1);
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 1 2
ps
删除操作会导致删除位置之后的元素前移。
RemoveAll
移除与指定的谓词所定义的条件相匹配的所有元素。
List<int> intList = new List<int> {1, 2, 3};
intList.RemoveAll(x => x > 1); // 删除所有大于1的元素
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 1
RemoveAt
移除 List<T> 的指定索引处的元素。
List<int> intList = new List<int> {1, 2, 3};
intList.RemoveAt(1); // void
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 1 3
Reverse
将 List<T> 或它的一部分中元素的顺序反转。
List<int> intList = new List<int> {1, 2, 3};
intList.Reverse();
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 3 2 1
TrimExcess
将容量设置为 List<T> 中元素的实际数目(如果该数目小于某个阈值)。
List<int> intList = new List<int>(16);
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:16
intList.Add(1);
intList.Add(2);
intList.Add(3);
intList.TrimExcess();
Console.WriteLine("intList 的当前容量为:{0}", intList.Capacity); // intList 的当前容量为:3
Sort
使用指定或默认的 IComparer<T> 实现或提供的 Comparison<T> 委托对 List<T> 中的元素或部分元素进行排序,以比较列表元素。
1、使用默认比较器对整个 List<T> 中的元素进行排序。
List<int> intList = new List<int> {4, 5, 1, 3, 2};
intList.Sort();
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 1 2 3 4 5
ps
- 默认升序;
- 不稳定排序;
- 即如果两个元素相等,则可能不会保留它们的顺序,相反,稳定排序会保留相等元素的顺序。
- 应用反省排序,规则如下:
- 如果分区大小小于或等于16个元素,则它将使用插入排序算法。
- 如果分区数超过2个 log n,其中 n 是输入数组的范围,则使用 Heapsort 算法。
- 否则,它将使用快速排序算法。
2、自定义排序
List<int> intList = new List<int> {2, 4, 1, 5, 3};
intList.Sort((x, y) => {
// return x.CompareTo(y); // 升序
return y.CompareTo(x); // 降序
});
foreach(var element in intList)
{
Console.Write(element + " ");
}
// 5 4 3 2 1
参考:
List<T> 类
集合和数据结构