.NET 常用的数据结构全在这(建议收藏)

222 阅读10分钟

前言

在 .NET 开发中,选择合适的数据结构对于提高程序的性能和可维护性至关重要。本文将介绍一些 .NET 中常用的内置数据结构,并探讨它们的特点和适用场景。

通过本文的学习,你将能够更好地选择和使用这些数据结构,从而编写出高效、可靠的代码。

正文

数据结构是计算机存储、组织数据的方式;在不同的场景中,通过分析写入查询的频率,选择合适的数据结构,对程序性能的提升有很大的帮助。

在 .NET 中,有许多常用的数据结构,它们实现了不同的接口以提供统一的数据访问方式。接口是标识功能的,不同的接口拆开是为了实现接口隔离原则,即使接口内容也可能重复。

IEnumerable 是任何数据集合都实现了的接口,为不同的数据结构提供了统一的数据访问方式,这是迭代器模式的体现。

1、数组(Array、ArrayList、List)

内存连续存储,节约空间,可以索引访问,读取快,增删慢。

Array

描述:在内存上连续分配的,而且元素类型是一样的。可以通过索引访问,读取快,增删慢,长度不变。

示例

int[] intArray = new int[3];
intArray[0] = 123;
string[] stringArray = new string[] { "123", "234" };

// 输出数组内容
Console.WriteLine(string.Join(", ", intArray)); // 输出: 123, 0, 0
Console.WriteLine(string.Join(", ", stringArray)); // 输出: 123, 234

ArrayList

描述:不定长的,连续分配的。元素没有类型限制,任何元素都是当成 object 处理,如果是值类型,会有装箱操作。读取快,增删慢。

示例

ArrayList arrayList = new ArrayList();
arrayList.Add("TianYa");
arrayList.Add(32); // Add 增加长度

// 删除数据
var value = arrayList[1]; // 获取索引为 1 的元素
arrayList.RemoveAt(0); // 删除索引为 0 的元素
arrayList.Remove("TianYa"); // 删除值为 "TianYa" 的元素

// 输出 ArrayList 内容
foreach (var item in arrayList)
{
    Console.WriteLine(item); // 输出: 32
}

List

描述:也是数组,内存上都是连续摆放,不定长,泛型,保证类型安全,避免装箱拆箱。读取快,增删慢。

示例

List<int> intList = new List<int>() { 1, 2, 3, 4 };
intList.Add(123);
intList.Add(123);

List<string> stringList = new List<string>();

// 尝试访问未初始化的元素会抛出异常
// stringList[0] = "123"; // 异常的

// 遍历并输出 List<int> 内容
foreach (var item in intList)
{
    Console.WriteLine(item); // 输出: 1, 2, 3, 4, 123, 123
}

小结

Array:固定大小的数组,内存连续分配,元素类型相同,通过索引访问。读取快,增删慢,长度不变。

ArrayList:动态数组,不定长,元素类型可以不同,内部使用 object 存储,可能会有装箱拆箱操作。读取快,增删慢。

List:动态数组,不定长,泛型,保证类型安全,避免装箱拆箱。读取快,增删慢。

2、链表(LinkedList、Queue、Stack)

在 .NET 中,链表是一种非连续存储的数据结构,每个元素(节点)不仅存储数据,还存储了指向下一个节点的地址。这种结构使得链表在插入和删除操作上非常高效,但在查找元素时需要进行顺序查找,因此读取速度较慢。

非连续摆放,存储数据+地址,找数据的话就只能顺序查找,读取慢,增删快。

LinkedList

描述:链表,泛型的特点,元素不连续分配,每个元素都有记录前后节点。节点值可以重复,不能通过索引访问,只能遍历查找元素,但增删操作非常方便。

示例

LinkedList<int> linkedList = new LinkedList<int>();
linkedList.AddFirst(123);
linkedList.AddLast(456);

bool isContain = linkedList.Contains(123);
LinkedListNode<int> node123 = linkedList.Find(123); // 元素123的位置,从头查找
linkedList.AddBefore(node123, 123);
linkedList.AddBefore(node123, 123);
linkedList.AddAfter(node123, 9);

linkedList.Remove(456);
linkedList.Remove(node123);
linkedList.RemoveFirst();
linkedList.RemoveLast();
linkedList.Clear();

// 输出 LinkedList 内容
foreach (var item in linkedList)
{
    Console.WriteLine(item);
}

Queue

描述:队列,先进先出(FIFO),底层实现为链表。适用于任务延迟执行等场景,例如 A 不断写入日志任务,B 不断获取任务去执行。

示例

Queue<string> numbers = new Queue<string>();
numbers.Enqueue("one"); // 入队
numbers.Enqueue("two");
numbers.Enqueue("three");
numbers.Enqueue("four");
numbers.Enqueue("five");

// 输出 Queue 内容
foreach (string number in numbers)
{
    Console.WriteLine(number);
}

Console.WriteLine($"Dequeuing '{numbers.Dequeue()}'"); // 出队
Console.WriteLine($"Peek at next item to dequeue: {numbers.Peek()}");
Console.WriteLine($"Dequeuing '{numbers.Dequeue()}'");

Queue<string> queueCopy = new Queue<string>(numbers.ToArray());
// 输出 Queue 复制的内容
foreach (string number in queueCopy)
{
    Console.WriteLine(number);
}

Console.WriteLine($"queueCopy.Contains(\"four\") = {queueCopy.Contains("four")}");
queueCopy.Clear();
Console.WriteLine($"queueCopy.Count = {queueCopy.Count}");

Stack

描述:栈,先进后出(LIFO),底层实现为链表。适用于解析表达式目录树等场景,操作记录为命令,撤销时是倒序的。

示例

Stack<string> numbers = new Stack<string>();
numbers.Push("one");
numbers.Push("two");
numbers.Push("three");
numbers.Push("four");
numbers.Push("five"); // 放进去,入栈

// 输出 Stack 内容
foreach (string number in numbers)
{
    Console.WriteLine(number);
}

Console.WriteLine($"Pop '{numbers.Pop()}'"); // 获取并移除,出栈
Console.WriteLine($"Peek at next item to dequeue: {numbers.Peek()}"); // 获取不移除
Console.WriteLine($"Pop '{numbers.Pop()}'");

Stack<string> stackCopy = new Stack<string>(numbers.ToArray());
// 输出 Stack 复制的内容
foreach (string number in stackCopy)
{
    Console.WriteLine(number);
}

Console.WriteLine($"stackCopy.Contains(\"four\") = {stackCopy.Contains("four")}");
stackCopy.Clear();
Console.WriteLine($"stackCopy.Count = {stackCopy.Count}");

小结

  • LinkedList:链表,泛型的特点,元素不连续分配,每个元素都有记录前后节点。节点值可以重复,不能通过索引访问,只能遍历查找元素,但增删操作非常方便。
  • Queue:队列,先进先出(FIFO),底层实现为链表。适用于任务延迟执行等场景,例如 A 不断写入日志任务,B 不断获取任务去执行。
  • Stack:栈,先进后出(LIFO),底层实现为链表。适用于解析表达式目录树等场景,操作记录为命令,撤销时是倒序的。

通过这些数据结构,我们可以根据具体需求选择合适的数据结构来优化性能和代码的可读性。希望这些示例和解释对你有所帮助!如果有任何其他问题或需要进一步的内容,请告诉我。

3、Set集合(HashSet、SortedSet)

在 .NET 中,Set 是一种纯粹的集合,用于存储唯一且无序的元素。Set 有两种主要的实现:HashSet 和 SortedSet。HashSet 提供了高效的元素插入、删除和查找操作,而 SortedSet 则在此基础上增加了元素的排序功能。

Set 纯粹的集合,容器,东西丢进去,唯一性,无序的

HashSet

描述:基于哈希表的集合,元素间没有关系,动态增加容量,去重。适用于需要快速插入、删除和查找的场景,如统计用户 IP、IP 投票、计算二次好友等。

示例

HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("123");
hashSet.Add("689");
hashSet.Add("456");
hashSet.Add("12435");
hashSet.Add("12435");
hashSet.Add("12435");

// 输出 HashSet 内容
foreach (var item in hashSet)
{
    Console.WriteLine(item);
}

Console.WriteLine($"Count: {hashSet.Count}");
Console.WriteLine($"Contains '12345': {hashSet.Contains("12345")}");

{
    HashSet<string> hashSet1 = new HashSet<string>();
    hashSet1.Add("123");
    hashSet1.Add("689");
    hashSet1.Add("789");
    hashSet1.Add("12435");
    hashSet1.Add("12435");
    hashSet1.Add("12435");

    hashSet1.SymmetricExceptWith(hashSet); // 补集
    hashSet1.UnionWith(hashSet); // 并集
    hashSet1.ExceptWith(hashSet); // 差集
    hashSet1.IntersectWith(hashSet); // 交集
}

// 转换为列表
List<string> list = hashSet.ToList();

// 清空集合
hashSet.Clear();

SortedSet

描述:排序的集合,去重且排序。适用于需要按特定顺序存储元素的场景,如统计排名等。

示例

SortedSet<string> sortedSet = new SortedSet<string>();
// IComparer<T> comparer  自定义对象要排序,就用这个指定
sortedSet.Add("123");
sortedSet.Add("689");
sortedSet.Add("456");
sortedSet.Add("12435");
sortedSet.Add("12435");
sortedSet.Add("12435");

// 输出 SortedSet 内容
foreach (var item in sortedSet)
{
    Console.WriteLine(item);
}

Console.WriteLine($"Count: {sortedSet.Count}");
Console.WriteLine($"Contains '12345': {sortedSet.Contains("12345")}");

{
    SortedSet<string> sortedSet1 = new SortedSet<string>();
    sortedSet1.Add("123");
    sortedSet1.Add("689");
    sortedSet1.Add("456");
    sortedSet1.Add("12435");
    sortedSet1.Add("12435");
    sortedSet1.Add("12435");

    sortedSet1.SymmetricExceptWith(sortedSet); // 补集
    sortedSet1.UnionWith(sortedSet); // 并集
    sortedSet1.ExceptWith(sortedSet); // 差集
    sortedSet1.IntersectWith(sortedSet); // 交集
}

// 转换为列表
List<string> list = sortedSet.ToList();

// 清空集合
sortedSet.Clear();

小结

  • HashSet:基于哈希表的集合,元素间没有关系,动态增加容量,去重。适用于需要快速插入、删除和查找的场景,如统计用户 IP、IP 投票、计算二次好友等。
  • SortedSet:排序的集合,去重且排序。适用于需要按特定顺序存储元素的场景,如统计排名等。

4、Key-Value(Hashtable、Dictionary、SortedDictionary、SortedList)

键值对(Key-Value)数据结构是一种高效的存储和检索方式。

常见的键值对数据结构包括 Hashtable、Dictionary、SortedDictionary 和 SortedList。

这些数据结构通过哈希散列技术实现了快速的读取和增删操作。

读取 & 增删都快?有哈希散列字典

Hashtable

描述:键值对集合,体积可以动态增加。使用键计算一个地址,然后将键值对放入该地址。如果不同的键得到相同的地址,第二个键值对会在前一个地址的基础上 + 1。

查找、增删改查都很快,但会浪费空间,数据量大时效率会下降。

基于数组实现,不是线程安全的。

示例

Hashtable table = new Hashtable();
table.Add("123", "456");
table[234] = 456;
table[234] = 567;
table[32] = 4562;
table[1] = 456;
table["TianYa"] = 456;

// 输出 Hashtable 内容
foreach (DictionaryEntry objDE in table)
{
    Console.WriteLine($"Key: {objDE.Key}, Value: {objDE.Value}");
}

// 线程安全
Hashtable synchronizedTable = Hashtable.Synchronized(table);

Dictionary

描述:泛型键值对集合,增删改查都很快,有序的。不是线程安全的,多线程环境下应使用 ConcurrentDictionary。

示例

Dictionary<int, string> dic = new Dictionary<int, string>();
dic.Add(1, "HaHa");
dic.Add(5, "HoHo");
dic.Add(3, "HeHe");
dic.Add(2, "HiHi");
dic.Add(4, "HuHu1");
dic[4] = "HuHu";

// 输出 Dictionary 内容
foreach (var item in dic)
{
    Console.WriteLine($"Key: {item.Key}, Value: {item.Value}");
}

SortedDictionary

描述:排序的键值对集合,增删改查都很快,按键值排序。适用于需要按键值排序的场景。

示例

SortedDictionary<int, string> sortedDic = new SortedDictionary<int, string>();
sortedDic.Add(1, "HaHa");
sortedDic.Add(5, "HoHo");
sortedDic.Add(3, "HeHe");
sortedDic.Add(2, "HiHi");
sortedDic.Add(4, "HuHu1");
sortedDic[4] = "HuHu";

// 输出 SortedDictionary 内容
foreach (var item in sortedDic)
{
    Console.WriteLine($"Key: {item.Key}, Value: {item.Value}");
}

SortedList

描述:排序的键值对集合,按键值排序。基于数组实现,增删改查都很快,但会浪费空间,数据量大时效率会下降。

示例

SortedList sortedList = new SortedList();
sortedList.Add("First", "Hello");
sortedList.Add("Second", "World");
sortedList.Add("Third", "!");
sortedList["Third"] = "~~";
sortedList.Add("Fourth", "!");
sortedList["Fourth"] = "!!!";

// 输出 SortedList 内容
foreach (DictionaryEntry entry in sortedList)
{
    Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}

// 最小化集合的内存开销
sortedList.TrimToSize();

// 删除元素
sortedList.Remove("Third");
sortedList.RemoveAt(0);
sortedList.Clear();

线程安全的集合

  • ConcurrentQueue:线程安全版本的队列。
  • ConcurrentStack:线程安全版本的栈。
  • ConcurrentBag:线程安全版本的对象集合。
  • ConcurrentDictionary:线程安全版本的字典。
  • BlockingCollection:线程安全的阻塞集合,支持生产者-消费者模式。

IEnumerable 和 IQueryable

  • IEnumerable:表示可以枚举的对象集合,延迟执行,遍历时才会去查询比较。
  • IQueryable:表示可以查询的对象集合,延迟执行,表达式树的解析,延迟到遍历的时候才去执行,适用于 LINQ to SQL 或 Entity Framework 的延迟查询。

示例

List<string> fruits = new List<string> { "apple", "passionfruit", "banana", "mango", "orange", "blueberry", "grape", "strawberry" };

// 使用 IEnumerable
IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);
foreach (var item in query)
{
    Console.WriteLine(item);
}

// 使用 IQueryable
IQueryable<string> queryable = fruits.AsQueryable().Where(s => s.Length > 5);
foreach (var item in queryable)
{
    Console.WriteLine(item);
}

小结

  • Hashtable:键值对集合,体积可以动态增加,查找、增删改查都很快,但会浪费空间,数据量大时效率会下降。基于数组实现,不是线程安全的。
  • Dictionary:泛型键值对集合,增删改查都很快,有序的。不是线程安全的,多线程环境下应使用 ConcurrentDictionary。
  • SortedDictionary:排序的键值对集合,增删改查都很快,按键值排序。适用于需要按键值排序的场景。
  • SortedList:排序的键值对集合,按键值排序。基于数组实现,增删改查都很快,但会浪费空间,数据量大时效率会下降。

总结

常用的数据结构有:数组(Array)、栈(Stack)、队列(Queue)、链表(Linked List)、树(Tree)、图(Graph)、堆(Heap)、哈希表(Hash)等,每种结构都有其独特的特性和适用场景。

数组(Array、ArrayList、List)

内存连续分配,读取快,但增删慢。Array 固定长度,ArrayList 和 List 动态扩展,其中 List 泛型,类型安全,避免装箱拆箱。

链表(LinkedList、Queue、Stack)

非连续分配,增删快,读取慢。LinkedList 双向链表,适合频繁增删;Queue 先进先出,适合任务队列;Stack 先进后出,适合表达式解析和命令撤销。

Set 集合(HashSet、SortedSet)

元素唯一,HashSet 基于哈希表,无序,插入、删除、查找快;SortedSet 基于平衡树,有序,同样高效。

Key-Value 集合(Hashtable、Dictionary、SortedDictionary、SortedList)

键值对存储,Hashtable 非泛型,存在装箱拆箱;Dictionary<TKey, TValue> 泛型,类型安全;

SortedDictionary<TKey, TValue> 有序,基于平衡树;SortedList<TKey, TValue> 有序,基于数组,数据量适中时效率较高。

.NET中常用的 Array、ArrayList、List、Dictionary、Queue 和 Stack 等数据结构已经可以满足大部分场景的需求。然而,对于性能要求较高的应用,选择合适的高级数据结构(如红黑树、B树、堆等)可以进一步优化程序的性能。选择合适的数据结构是编程中的一项重要技能,能够显著提升程序的效率和可维护性。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!