Csharp中的常见的数据结构
数据结构
网站开发,都是上层应用;数据结构:属于底层的各种数据的存储方式;
1.数据计算,业务逻辑处理。。。。。---基于数据来来的
2.怎么保存、怎么查询、怎么删除、更新。。。。
3.就需要一些规范,定义各种规范,把数据做保存。。。。
数据结构:
1.Set集合:纯粹的容器;无需存储,就是一个容器
2.线型结构:在存储的时候;一对一存储;
3.树形结构:表达式目录树(二叉树)、菜单结构:一对多
4.图状结构:拓扑图、网状结构(地图开发,用的上)
常见的数据结构
线程结构:Array/ArrayList/List/LinkedList/Queue/Stack/HastSet/SortedSet/Hashtable/SortedList/Dictionary/SortedDictionary
IEnumerable、ICollection、IList、IQueryable
接口是标识功能的,不同的接口拆开,就是为接口隔离;虽然我们接口内容也可能重复
IEnumerable 任何数据集合,都实现了的,为不同的数据结构,提供了统一的数据访问方式
这个就是迭代器模式
一、线性结构
- 线程结构:Array/ArrayList/List/LinkedList/Queue/Stack/HastSet/SortedSet/Hashtable/SortedList/Dictionary/SortedDictionary
数组:内存连续存储,节约空间,可以索引访问,读取快,删慢
Array
Array:在内存上连续分配的,而且元素类型是一样的
可以坐标访问 读取快--增删慢,长度不变
Console.WriteLine("***************Array******************");
int[] intArray = new int[3];
intArray[0] = 123;
string[] stringArray = new string[] { "123", "234" };//Array
foreach (var item in stringArray)
{
}
for (int i = 0; i < intArray.Length; i++)
{
Console.WriteLine(intArray[i]);
}
ArrayList
ArrayList:以前的开发中使用的比较多 不定长的,连续分配的;
元素没有类型限制,任何元素都是当成object处理,如果是值类型,会有装箱操作
读取快--增删慢
Console.WriteLine("***************ArrayList******************");
ArrayList arrayList = new ArrayList();
arrayList.Add("Richard");
arrayList.Add("Is");
arrayList.Add(32);//add增加长度
// arrayList[4] = 26;//索引复制,不会增加长度
//删除数据
//arrayList.RemoveAt(4);
var value = arrayList[2];
arrayList.RemoveAt(0);
arrayList.Remove("Richard");
foreach (var item in arrayList)
{
}
for (int i = 0; i < arrayList.Count; i++)
{
Console.WriteLine(arrayList[i]);
}
List
List:也是Array,内存上都是连续摆放;不定长;泛型,保证类型安全,避免装箱拆箱; 性能也是比Arraylist要高
读取快--增删慢
Console.WriteLine("***************List<T>******************");
List<int> intList = new List<int>() { 1, 2, 3, 4 };
intList.Add(123);
intList.Add(123);
//intList.Add("123");
//intList[0] = 123;
List<string> stringList = new List<string>();
//stringList[0] = "123";//异常的
foreach (var item in intList)
{
}
for (int i = 0; i < intList.Count; i++)
{
Console.WriteLine(intList[i]);
}
以上特点:读取快,增删相对慢;
链表:非连续摆放,存储数据+地址,找数据的话就只能顺序查找,读取慢;增删快
LinkedList
LinkedList:泛型的特点;链表,元素不连续分配,每个元素都有记录前后节点
节点值可以重复
能不能索引访问?不能,
1.查询元素就只能遍历 查找不方便--查询慢
2.增删 就比较方便--增删快
Console.WriteLine("***************LinkedList<T>******************");
LinkedList<int> linkedList = new LinkedList<int>();
//linkedList[3] //不能索引访问--不是数组
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();
Queue
Queue:队列,就跟一个没有瓶底的瓶子一样; 就是链表 先进先出 放任务延迟执行,A不断写入日志任务 B不断获取任务去执行
Console.WriteLine("***************Queue<T>******************");
Queue<string> numbers = new Queue<string>();
numbers.Enqueue("one");
numbers.Enqueue("two");
numbers.Enqueue("three");
numbers.Enqueue("four");
numbers.Enqueue("four");
numbers.Enqueue("five");
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());
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
队列是没瓶底的瓶子,栈是有瓶底的瓶子
Stack 就是链表 先进后出 解析表达式目录树的时候,先产生的数据后使用
操作记录为命令,撤销的时候是倒序的
Console.WriteLine("***************Stack<T>******************");
Stack<string> numbers = new Stack<string>();
numbers.Push("one");
numbers.Push("two");
numbers.Push("three");
numbers.Push("four");
numbers.Push("five");//放进去
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());
foreach (string number in stackCopy)
{
Console.WriteLine(number);
}
Console.WriteLine($"stackCopy.Contains(\"four\") = {stackCopy.Contains("four")}");
stackCopy.Clear();
Console.WriteLine($"stackCopy.Count = {stackCopy.Count}");
hash表类型
set
集合:hash分布,元素间没关系,动态增加容量 去重--如果是同一个引用,就可以去掉重复;
应用场景:抖音发布的作品点赞!统计用户IP;IP投票
提供了一些计算:交叉并补--二次好友/间接关注/粉丝合集
应用场景:一叶知秋:Richard 系统可能推荐一些可能认识的人:找出Richard老师的好友列表:找出一叶知秋这个同学的好友列表:求差集;---是一叶知秋的好友,但是不是Richard好友。系统就给Richard推荐:可能认识的人;
Console.WriteLine("***************HashSet<string>******************");
HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("123");
hashSet.Add("689");
hashSet.Add("456");
string s1 = "12345";
hashSet.Add(s1);
string s2 = "12345";
hashSet.Add(s2);
string s3 = "12345";
hashSet.Add(s3);
//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();
HashSet<People> peoples = new HashSet<People>();
People people = new People()
{
Id = 123,
Name = "小菜"
};
People people1 = new People()
{
Id = 123,
Name = "小菜"
};
peoples.Add(people);
peoples.Add(people1);
peoples.Add(people1);
SortedSet
排序的集合:去重 而且排序
统计排名--每统计一个就丢进去集合
Console.WriteLine("***************SortedSet<string>******************");
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");
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,一段连续有限空间放value(开辟的空间比用到的多,hash是用空间换性能),基于key散列计算得到地址索引,这样读取快
增删也快,删除时也是计算位置,增加也不影响别人
因为key 是最终生成了索引的;如果数量过多,散列计算后,肯定会出现不同的key计算出的索引只是同一个
肯定会出现2个key(散列冲突),散列结果一致18,可以让第二次的+1,
可能会造成效率的降低,尤其是数据量大的情况下,以前测试过dictionary在3w条左右性能就开始下降的厉害
HashTable
Hashtable key-value 体积可以动态增加 拿着key计算一个地址,然后放入key - value
object-装箱拆箱 如果不同的key得到相同的地址,第二个在前面地址上 + 1
查找的时候,如果地址对应数据的key不对,那就 + 1查找。。
浪费了空间,Hashtable是基于数组实现
查找个数据 一次定位; 增删 一次定位; 增删查改 都很快
浪费空间,数据太多,重复定位定位,效率就下去了
Console.WriteLine("***************Hashtable******************");
Hashtable table = new Hashtable();
table.Add("123", "456");
//table.Add("123", "456");//key相同 会报错
table[234] = 456;
table[234] = 567;
table[32] = 4562;
table[1] = 456;
table["Richard"] = 456;
foreach (DictionaryEntry objDE in table)
{
Console.WriteLine(objDE.Key.ToString());
Console.WriteLine(objDE.Value.ToString());
}
//线程安全
Hashtable.Synchronized(table);//只有一个线程写 多个线程读
字典
字典:泛型;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";
//dic.Add(4, "HuHu");
foreach (var item in dic)
{
Console.WriteLine($"Key:{item.Key}, Value:{item.Value}");
}
SortedDictionary
Console.WriteLine("***************SortedDictionary******************");
SortedDictionary<int, string> dic = new SortedDictionary<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";
//dic.Add(4, "HuHu");
foreach (var item in dic)
{
Console.WriteLine($"Key:{item.Key}, Value:{item.Value}");
}
线程安全版的数据结构
ConcurrentQueue 线程安全版本的Queue
ConcurrentStack线程安全版本的Stack
ConcurrentBag线程安全的对象集合
ConcurrentDictionary线程安全的Dictionary
BlockingCollection
这里会产生一个问题,为什么有那么多的数据结构,各种各样都有不同,都能使用foreach去遍历,这就是迭代器模式
定义一个通用迭代器的类型:
public interface IIterator<T>
{
/// <summary>
/// 当前的对象
/// </summary>
T Current { get; }
/// <summary>
/// 移动到下一个对象,是否存在
/// </summary>
/// <returns></returns>
bool MoveNext();
/// <summary>
/// 重置
/// </summary>
void Reset();
}
实现的food类
public class Food
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
}
实现的KFC和MacDonald的菜单的迭代器
KFCMenu.cs
public class KFCMenu
{
private Food[] _FoodList = new Food[3];
public KFCMenu()
{
this._FoodList[0] = new Food()
{
Id = 1,
Name = "汉堡包",
Price = 15
};
this._FoodList[1] = new Food()
{
Id = 2,
Name = "可乐",
Price = 10
};
this._FoodList[2] = new Food()
{
Id = 3,
Name = "薯条",
Price = 8
};
}
public Food[] GetFoods()
{
return this._FoodList;
}
public IIterator<Food> GetEnumerator()
{
return new KFCMenuIterator(this);
}
}
MacDonaldMenu.cs
/// <summary>
/// 麦当劳菜单 金拱门
/// </summary>
public class MacDonaldMenu
{
private List<Food> _FoodList = new List<Food>();
public MacDonaldMenu()
{
this._FoodList.Add(new Food()
{
Id = 1,
Name = "鸡肉卷",
Price = 15
});
this._FoodList.Add(new Food()
{
Id = 2,
Name = "红豆派",
Price = 10
});
this._FoodList.Add(new Food()
{
Id = 3,
Name = "薯条",
Price = 9
});
}
public List<Food> GetFoods()
{
return this._FoodList;
}
public IIterator<Food> GetEnumerator()
{
return new MacDonaldIterator(this);
}
}
来调用我们实现的迭代器,使用foreach循环
Console.WriteLine("********************KFCMenu********************");
KFCMenu kFCMenu = new KFCMenu();
IIterator<Food> iterator = kFCMenu.GetEnumerator();
foreach (var item in kFCMenu)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
Console.WriteLine(item.Price);
}
while (iterator.MoveNext())
{
Console.WriteLine("再来一次");
Food food = iterator.Current;
Console.WriteLine(food.Name);
}
Console.WriteLine("********************MacDonaldMenu********************");
MacDonaldMenu macDonaldMenu = new MacDonaldMenu();
IIterator<Food> iterator1 = macDonaldMenu.GetEnumerator();
foreach (var item in macDonaldMenu)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
Console.WriteLine(item.Price);
}
while (iterator1.MoveNext())
{
Console.WriteLine("再来一次");
Food food = iterator1.Current;
Console.WriteLine(food.Name);
}
}
Foreach循环:其实都是通过实现:IEnumerable接口来完后;实现接口,就需要实现一个GetIEnumerator方法;这个方法返回的是一个Enumerator---迭代器;就是这个迭代器,提供了一统一对线型结构数据的一种访问方式;
yield
Yield是语法糖,编译时由编译器生成Iterrator的代码,包括movenext current reset
定义一个类,方便我们去理解
public class YieldDemo
{
public IEnumerable<int> Power()
{
for (int i = 0; i < 10; i++)
{
yield return this.Get(i);
Console.WriteLine("yield这里再来一次");
// yield return this.Get(i) + 1;
}
}
public IEnumerable<int> Common()
{
List<int> intList = new List<int>();
for (int i = 0; i < 10; i++)
{
intList.Add(this.Get(i));
Console.WriteLine("集合这里再来一次");
}
return intList;
}
private int Get(int num)
{
Thread.Sleep(2000);
return num * DateTime.Now.Second;
}
}
对应的调用的代码
Console.WriteLine("*******************Yield********************");
YieldDemo yieldDemo = new YieldDemo();
foreach (var item in yieldDemo.Power())
{
Console.WriteLine(item);//按需获取,要一个拿一个
if (item > 100)
break;
}
Console.WriteLine("*******************************************");
foreach (var item in yieldDemo.Common())
{
Console.WriteLine(item);//先全部获取,然后一起返还
if (item > 100)
break;
}
含有yield的函数说明它是一个生成器,而不是普通的函数。当程序运行到yield这一行时,该函数会返回值,并保存当前域的所有变量状态;等到该函数下一次被调用时,会从上一次中断的地方开始执行,一直遇到下一个yield, 程序返回值, 并在此保存当前状态; 如此反复,直到函数正常执行完成。
迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素 而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)
--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。
在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接 口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是 数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。
dynamic
动态dynamic解读:framework4.0 让程序有了弱类型的特点
示例代码
{
string s = "abcd";
//int i = (int)s; //强类型:编译时完成安全检查
//s.Hello();
}
{
dynamic s = "abcd";//弱类型:运行时才检查类型
int i = (int)s;
s.Hello();
int a = s.Richard;
}
{
object A = new YieldDemo();
//A.Power();
Type type = A.GetType();
MethodInfo method = type.GetMethod("Power");
method.Invoke(A, null);
dynamic dA = A;
dA.Power();
}
1 代替反射 2 数据绑定方便 3 跟C++交互方便
性能比反射高
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?