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 任何数据集合,都实现了的,为不同的数据结构,提供了统一的数据访问方式
这个就是迭代器模式

一、线性结构

  1. 线程结构: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++交互方便
性能比反射高

posted @ 2024-05-30 15:02  飘雨的河  阅读(10)  评论(0编辑  收藏  举报