c# 开发中最常用的几种数据结构
说明:以下程序基于 vs2019 .Net Framework4.7.2 下进行测试。
一、内存上连续存储,节约空间,可以索引访问,读取快,增删慢
1.Array 数组,是内存上连续分配的一组数据对象
优点:可以根据下标读取和写入,读取速度快
缺点:写入较慢,固定长度,只能存储一种基础数据类型
//Array:在内存上连续分配的,而且元素类型是一样的 //可以坐标访问 读取快--增删慢,长度不变 Console.WriteLine("***************Array******************"); int[] intArray = new int[3]; intArray[0] = 1; intArray[0] = 12; intArray[0] = 123; string[] strArray = new string[] { "1", "2" };//Array
2.ArrayList 数组,是内存上连续分配的一组数据对象
优点:不固定长度,可以根据数据自动改变大小,可以根据下标读取和写入,读取速度快
缺点:所有数据类型都会当作object类型处理,有装箱和拆箱操作,性能较慢,,不是类型安全的
//ArrayList 不定长的,连续分配的; //元素没有类型限制,任何元素都是当成object处理,如果是值类型,会有装箱操作 //读取快--增删慢 Console.WriteLine("***************ArrayList******************"); System.Collections.ArrayList arrayList = new System.Collections.ArrayList(); arrayList.Add(true); arrayList.Add("a"); arrayList.Add(32);//add增加长度 //arrayList[4] = 26;//索引复制,不会增加长度 //删除数据 //arrayList.RemoveAt(4); var value = arrayList[2]; arrayList.RemoveAt(0); arrayList.Remove(true);
3.List 数组,是内存上连续分配的一组数据对象
优点:不固定长度,可以根据数据自动改变大小,读取速度快
缺点:只能存储一种基础数据类型,增删较慢,,是类型安全的
//List:也是Array,内存上都是连续摆放;不定长;泛型,保证类型安全,避免装箱拆箱 //读取快--增删慢 Console.WriteLine("***************List<T>******************"); List<int> intList = new List<int>() {}; intList.Add(1); intList.Add(2); intList.Add(3); foreach(var i in intList) { Console.WriteLine(i.ToString()); }
二、非连续摆放,存储数据+地址,找数据的话就只能顺序查找,读取慢;增删快
1.LinkedLis 泛型链表,是内存上不连续分配的一组数据对象
优点:节点值可以重复,增删改数据时,不需要移动前后数据位置,性能较快,是类型安全的
缺点:不能通过下标访问,查找数据只能按照顺序查找,查询较慢,只能存储一种基础数据类型
//LinkedList:泛型的特点;链表,元素不连续分配,每个元素都有记录前后节点 //节点值可以重复 //能不能下标访问?不能,找元素就只能遍历 查找不方便 //增删 就比较方便 Console.WriteLine("***************LinkedList<T>******************"); LinkedList<int> linkedList = new LinkedList<int>(); //linkedList[3] linkedList.AddFirst(1);//添加为第一个节点元素 linkedList.AddFirst(1);//添加为第一个节点元素 linkedList.AddLast(3);//添加为最后一个节点元素 bool isContain = linkedList.Contains(1); LinkedListNode<int> node1 = linkedList.Find(1); //元素123的位置 从头查找 linkedList.AddBefore(node1, 0);//节点不存在,会报错 linkedList.AddBefore(node1, 0);//在指定节点前新增元素 linkedList.AddAfter(node1, 2);//在指定节点后新增元素 linkedList.Remove(3);//删除指定值,不存在会报错 linkedList.Remove(node1);//删除指定节点 linkedList.RemoveFirst();//删除第一个元素 linkedList.RemoveLast();//删除最后一个元素 linkedList.Clear();
2.Queue 队列,其实也是一种特殊链表,数据是先进先出(我们可以想象一下把数据放进水管里面))
适用场景:业务模块解耦,提高系统负载,系统并发的处理能力。
//Queue 就是链表 先进先出 放任务延迟执行,A不断写入日志任务 B不断获取任务去执行 Console.WriteLine("***************Queue<T>******************"); Queue<string> numbers = new Queue<string>(); numbers.Enqueue("1");//添加对象到队列末尾 numbers.Enqueue("2"); numbers.Enqueue("3"); numbers.Enqueue("4"); numbers.Enqueue("5"); numbers.Enqueue("6"); foreach (string number in numbers) { Console.WriteLine(number); } Console.WriteLine($"Dequeuing '{numbers.Dequeue()}'");//Dequeue方法 移除并返回队列的第一个元素 Console.WriteLine($"Peek at next item to dequeue: { numbers.Peek()}");//Peek方法 返回队列的第一个元素 但不移除元素 Console.WriteLine($"Dequeuing '{numbers.Dequeue()}'"); Queue<string> queueCopy = new Queue<string>(numbers.ToArray()); foreach (string number in queueCopy) { Console.WriteLine(number); } Console.WriteLine($"queueCopy.Contains(\"4\") = {queueCopy.Contains("4")}"); queueCopy.Clear(); Console.WriteLine($"queueCopy.Count = {queueCopy.Count}");
2.Stack 栈,其实也是一种特殊链表,和队列不一样的是数据是先进后出(我们可以想象一下把数据放进瓶子里面)
适用场景:需要进行先进后出的数据处理时。
//队列是水管,栈是有瓶底的瓶子 { //Stack 就是链表 先进后出 解析表达式目录树的时候,先产生的数据后使用 //操作记录为命令,撤销的时候是倒序的 Console.WriteLine("***************Stack<T>******************"); Stack<string> numbersStack = new Stack<string>(); numbersStack.Push("1"); numbersStack.Push("2"); numbersStack.Push("3"); numbersStack.Push("4"); numbersStack.Push("5");//放进去 foreach (string number in numbersStack) { Console.WriteLine(number); } Console.WriteLine($"Pop '{numbersStack.Pop()}'");//获取并移除 Console.WriteLine($"Peek at next item to dequeue: { numbersStack.Peek()}");//获取不移除 Console.WriteLine($"Pop '{numbersStack.Pop()}'"); Stack<string> stackCopy = new Stack<string>(numbersStack.ToArray()); foreach (string number in stackCopy) { Console.WriteLine(number); } Console.WriteLine($"stackCopy.Contains(\"4\") = {stackCopy.Contains("4")}"); stackCopy.Clear(); Console.WriteLine($"stackCopy.Count = {stackCopy.Count}"); }
三、Set 纯粹的集合,容器,唯一性
1.hashset 元素之间没有关联,hash分布,自动增加容量大小,自动去重
使用场景:统计投票明细,关注列表/粉丝集合等
//集合:hash分布,元素间没关系,动态增加容量 去重 //统计用户IP;IP投票 交叉并补--二次好友/间接关注/粉丝合集 Console.WriteLine("***************HashSet<string>******************"); HashSet<string> hashSet = new HashSet<string>(); hashSet.Add("1"); hashSet.Add("2"); hashSet.Add("3"); hashSet.Add("4"); hashSet.Add("5"); hashSet.Add("5"); //hashSet[0]; foreach (var item in hashSet) { Console.WriteLine(item); } Console.WriteLine(hashSet.Count); Console.WriteLine(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);//交 // 找出共同的好友 } hashSet.ToList(); hashSet.Clear();
2.SortedSet 排序的集合,自动去重,而且是有序的
使用场景:统计排名--每统计一个就丢进去集合,各类排行榜
//排序的集合:去重 而且排序 //统计排名--每统计一个就丢进去集合 Console.WriteLine("***************SortedSet<string>******************"); SortedSet<string> sortedSet = new SortedSet<string>(); //IComparer<T> comparer 自定义对象要排序,就用这个指定 sortedSet.Add("1"); sortedSet.Add("2"); sortedSet.Add("3"); sortedSet.Add("4"); sortedSet.Add("5"); sortedSet.Add("5"); foreach (var item in sortedSet) { Console.WriteLine(item); } Console.WriteLine(sortedSet.Count); Console.WriteLine(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);//交 } sortedSet.ToList(); sortedSet.Clear();
四、有没有读取&增删都比较快的数据类型呢? 哎,还真有有,我们来看看这个 hash散列 字典
key-value结构存储,动态扩充大小,一段连续有限空间存储(hash是用空间换性能),根据key的散列值计算得到存储值的的索引,这样查询,增删改都比较快。不像数组那样需要移动相邻元素
所以效率比较高,基于key计算散列值,可能会出现重复情况(第二个散列值自动在前面+1),数据量大的话,性能会下降。
1.HashTable
优点:查询,增删改比较快,
缺点:存储的key和value都是 object 类型,有装箱和拆箱操作,影响性能。
//Hashtable key-value 体积可以动态增加 拿着key计算一个地址,然后放入key - value //object-装箱拆箱 如果不同的key得到相同的地址,第二个在前面地址上 + 1 //查找的时候,如果地址对应数据的key不对,那就 + 1查找。。 //浪费了空间,Hashtable是基于数组实现 //查找个数据 一次定位; 增删 一次定位; 增删查改 都很快 //浪费空间,数据太多,重复定位定位,效率就下去了 Console.WriteLine("***************Hashtable******************"); System.Collections.Hashtable table = new System.Collections.Hashtable(); table.Add("1", "1111111111111111"); table[234] = 234; table[567] = 567; table["eleven"] = 456; foreach (System.Collections.DictionaryEntry objDE in table) { Console.WriteLine(objDE.Key.ToString()); Console.WriteLine(objDE.Value.ToString()); } //线程安全 System.Collections.Hashtable.Synchronized(table);//只有一个线程写 多个线程读
2.Dictionary字典:泛型;key - value,增删查改 都很快;有序的
//字典:泛型;key - value,增删查改 都很快;有序的 // 字典不是线程安全 ConcurrentDictionary Console.WriteLine("***************Dictionary******************"); 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";//相同key替换原值 dic.Add(4, "HuHu");//相同key,会报错 foreach (var item in dic) { Console.WriteLine($"Key:{item.Key}, Value:{item.Value}"); }
3.SortedDictionary 排序字典
Console.WriteLine("***************SortedDictionary******************"); SortedDictionary<int, string> dicS = new SortedDictionary<int, string>(); dicS.Add(1, "HaHa"); dicS.Add(5, "HoHo"); dicS.Add(3, "HeHe"); dicS.Add(2, "HiHi"); dicS.Add(4, "HuHu1"); dicS[4] = "HuHu";//相同key替换原值 dicS.Add(4, "HuHu");//相同key,会报错 foreach (var item in dicS) { Console.WriteLine($"Key:{item.Key}, Value:{item.Value}"); }
4.SortedList 排序集合
Console.WriteLine("***************SortedList******************"); System.Collections.SortedList sortedList = new System.Collections.SortedList();//IComparer sortedList.Add("First", "Hello"); sortedList.Add("Second", "World"); sortedList.Add("Third", "!"); sortedList["Third"] = "~~";//相同key替换原值 sortedList.Add("Fourth", "!"); sortedList.Add("Fourth", "!");//重复的Key Add会错 sortedList["Fourth"] = "!!!"; var keyList = sortedList.GetKeyList(); var valueList = sortedList.GetValueList(); sortedList.TrimToSize();//用于最小化集合的内存开销 sortedList.Remove("Third"); sortedList.RemoveAt(0); sortedList.Clear();
五、线程安全的几种数据结构
//ConcurrentQueue 线程安全版本的Queue //ConcurrentStack 线程安全版本的Stack //ConcurrentBag 线程安全的对象集合 //ConcurrentDictionary 线程安全的Dictionary //BlockingCollection
六、总结
如果实现了IList了的就可以用下标访问,实现IConllection了的可以用Add添加
数据结名称 | 声明方式 | 优点,缺点 | 是否线程安全 | 线程安全的版本 |
Array(数组) | 固定长度,读取快,增删慢 | 否 | ||
ArrayList(动态数组) | 不固定长度,动态长度,读取快,增删慢 | 否 | ||
List(泛型集合) | 不固定长度,动态长度,读取快,增删慢 | 否 | ||
SortedList | 不固定长度,动态长度,读取快,增删慢 | 否 | ||
LinkedList(双向链表) |
1、链表在内存中的空间不是连续的,每块空间称作一个节点,每个节点都存有与它之前和之后相连接的节点的地址,因此向链表中添加和删除元素时只需要更改相关节点存储的地址的指向,效率高
2、查找元素时不能通过下标访问,只能从头开始通过地址按顺序查找,效率低
|
否 | ||
Queue(队列) | 先进先出的数据结构 | 否 | ConcurrentQueue | |
Stack(栈) | 先进后出的数据结构 | 否 | ConcurrentStack | |
Dictionary(字典) | 键值对形式存储,读取,增删快,消耗内存 | 否 | ConcurrentDictionary | |
SortDictionary | 排序的字典 | 否 | ||
Hashtable(哈希表) | 否 | BlockingCollection | ||
HashSet(哈希集合) | 否 | ConcurrentBag |